图相关习题 判断联通性
主要考如何抽象为图
找两点之间最短路 没有负权的 路 dijkstra一定是对的
环图
例题1 镜子田地 简单图
分析:需要将田地分成 田地内层 和 外层 以及将镜子分开的两片田地抽象成点
当光线折射就是边 这样就抽象成为了图了。度为0,就是直接出去,
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};//四个偏移量 方向上0右1下1左2
int n,m;
const int N = 1005;
char mp[N][N];
int dfs(int x,int y,int d){
if(x<0||y<0||x>=n ||y>=m) return 0;
//判断出每个方向
if(mp[x][y]=='/') d^=1;//1位异或操作
else d^=3;//两位异或操作
return dfs(x+dx[d],y+dy[d],d)+1;//下一步
}
int main()
{
cin>>n>>m;
for (int i = 0; i < n; i ++ ) scanf("%s",mp[i]);//输入图 这样输入
int res=0;
for (int i = 0; i < n; i ++ ){
res=max(res,dfs(i,0,1));//光往右射进第一列
res=max(res,dfs(i,m-1,3));//方向光往左射进最后一列
}
for (int i = 0; i < m; i ++ ){
res=max(res,dfs(0,i,2));
res=max(res,dfs(n-1,i,0));
}
cout << res<<"\n";
return 0;
}
拖拉机 https://www.acwing.com/problem/content/2021/
有障碍物 求达到原点需要经过的最小障碍物的值
双端队列注意点:1.边权为0放队首 为1放队尾
2.只有被更新的点才有资格放入队列(因为不被更新说明已经放入进去了,已经被更新过了,且这次不需要更新)
3.第一次出列的是最短点 会有冗余(多次被更新) 但是此题不需要处理 只需要0,0一出列就返回即可
#include <iostream>
#include <cstring>
#include <algorithm>
#include <deque>//双端队列广搜的头文件
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 1010;//定义矩阵边长
bool g[N][N], vis[N][N];//g 障碍物
int dist[N][N];
int bfs(int sx, int sy)
{
deque<PII> q;//定义双端队列
q.push_back({sx, sy});//起点加到双端队列
memset(dist, 0x3f, sizeof dist);//把距离初始化成较大值
dist[sx][sy] = 0;
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};//顺序:上右下左
while (q.size())//当队列不空
{
auto t = q.front();//取出队头元素
q.pop_front();//删除队头元素
if (vis[t.x][t.y]) continue;//判断当前点是否被搜过
vis[t.x][t.y] = true;//没搜过就标记,防止未来重复搜索
if (!t.x && !t.y) break;//优化:已经走到终点,break
for (int i = 0; i < 4; i ++ )
{
int x = t.x + dx[i], y = t.y + dy[i];//求当前方向坐标
if (x >= 0 && x < N && y >= 0 && y < N)
{
int w = 0;//权值不一定是1,需要检查
if (g[x][y]) w = 1;//如果下一个点是障碍物,权值变成1
if (dist[x][y] > dist[t.x][t.y] + w)//如果到达下一个点的距离能变小
{
dist[x][y] = dist[t.x][t.y] + w;//更新权值
if (!w) q.push_front({x, y});//w=0,加到队头
else q.push_back({x, y});//否则,加到队尾
}
}
}
}
return dist[0][0];
}
int main()
{
int n, sx, sy;//n是障碍物数量,sx,sy是起点
scanf("%d%d%d", &n, &sx, &sy);
while (n -- )
{
int x, y;
scanf("%d%d", &x, &y);//读入障碍物的坐标
g[x][y] = true;
}
printf("%d\n", bfs(sx, sy));//输出最短距离
return 0;
}
大型植被修复https://www.acwing.com/activity/content/problem/content/6586/
作为一名奶农,Farmer John 想要确保他的每头奶牛都能得到丰富的食谱。
他的 M 头奶牛每一头都有两块喜爱的草地,他想要确保这两块草地种植不同种类的草,从而每头奶牛都可以选择两种草。
已知每块草地最多被 3 头奶牛喜爱。//重点 最后 每条边 只剩下一种情况了 不需要回溯
请帮助 Farmer John 选择每块草地所种的草的种类,使得所有奶牛的营养需求都得到满足。
输出一个 N 位数,每一位均为 1…4 之一,表示每一块草地上所种的草的种类。
等价于
给出n个点和m条边 从前往后枚举 找到第一个可以种的草 然后去掉当前草地的相邻的草地去掉刚才草地的方案
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110, M = 310;//
int n, m;
int h[N], e[M], ne[M], idx;//h是n个连接表的结点头 e是所有的边 ne是每个结点的next值是多少
bool st[N][5];//背选方案
void add(int a, int b)//加边函数
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;//h
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
while (m -- )
{
int a, b;
cin >> a >> b;
add(a, b), add(b, a);//加边
}
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= 4; j ++ )//从第一种枚举
if (!st[i][j])//这种颜色没有删除过
{
cout << j;//输出
for (int u = h[i]; ~u; u = ne[u])//邻边 h是出度 u 是e
st[e[u]][j] = true;
break;
}
return 0;
}
奶牛工厂 https://www.acwing.com/problem/content/description/1473/
floyd算法 判断点和点之间是否联通
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110;
int n;
int g[N][N];
int main()
{
cin >> n;
for (int i = 1; i <= n; i ++ ) g[i][i] = 1;//可以到自身
for (int i = 0; i < n - 1; i ++ )
{
int a, b;
cin >> a >> b;
g[a][b] = 1;//a->b
}
for (int k = 1; k <= n; k ++ )
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
if (g[i][k] && g[k][j])//如果i能到k k能到j 说明i能到达j
g[i][j] = 1;
for (int i = 1; i <= n; i ++ )//遍历每个点查看是不是这个答案
{
bool flag = true;
for (int j = 1; j <= n; j ++ )//以其他点为起点看 能否到达答案
if (!g[j][i])//一旦发现有一个到达不是1 说明不能到达 不符合
{
flag = false;
break;
}
if (flag)
{
cout << i << endl;
return 0;
}
}
puts("-1");
return 0;
}
dijstra on^2
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 510;
int n, m;
int g[N][N];
int dist[N];
bool st[N];
int dijkstra()
{
memset(dist, 0x3f, sizeof dist);//初始化距离
dist[1] = 0;//题目说明从第一个点走 第一个点为0
for (int i = 0; i < n - 1; i ++ )//迭代n次
{
int t = -1;
//下面这个for是找最小值
for (int j = 1; j <= n; j ++ )//遍历每个点找到 没选过 并且距离最小的点
if (!st[j] && (t == -1 || dist[t] > dist[j]))//如果这个点没被走过并且到达t的距离大于到达j的距离
//这里t==-1的判断一定要有
t = j;
//这个for看是不是能更新,所以堆优化 能找到最小值 邻接表能找到可以更新的点
for (int j = 1; j <= n; j ++ )//遍历每个点看看这个点的距离能不能更新
dist[j] = min(dist[j], dist[t] + g[t][j]);
st[t] = true;//做完这些在把他选择表示以及选过
}
if (dist[n] == 0x3f3f3f3f) return -1;//到达n的距离还是0 说明没被更新过 即没有到达这个点的边
return dist[n];
}
int main()
{
scanf("%d%d", &n, &m);
memset(g, 0x3f, sizeof g);//初始话图
while (m -- )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
g[a][b] = min(g[a][b], c);//处理对于重边 只存距离小的边
}
printf("%d\n", dijkstra());
return 0;
}
dijkstra堆优化 适合系数 图 omlogn
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
typedef pair<int, int> PII;
const int N = 1000010; // 把N改为150010就能ac
// 稀疏图用邻接表来存 点数量多 就用邻接表
int h[N], e[N], ne[N], idx;
int w[N]; // 用来存权重
int dist[N];
bool st[N]; // 如果为true说明这个点的最短路径已经确定
int n, m;
void add(int x, int y, int c)
{
w[idx] = c; // 有重边也不要紧,假设1->2有权重为2和3的边,再遍历到点1的时候2号点的距离会更新两次放入堆中
e[idx] = y; // 这样堆中会有很多冗余的点,但是在弹出的时候还是会弹出最小值2+x(x为之前确定的最短路径),并
ne[idx] = h[x]; // 标记st为true,所以下一次弹出3+x会continue不会向下执行。
h[x] = idx++;
}
int dijkstra()
{
memset(dist, 0x3f, sizeof(dist));
dist[1] = 0;
priority_queue<PII, vector<PII>, greater<PII>> heap; // 定义一个小根堆
// 这里heap中为什么要存pair呢,首先小根堆是根据距离来排的,所以有一个变量要是距离,其次在从堆中拿出来的时
// 候要知道知道这个点是哪个点,不然怎么更新邻接点呢?所以第二个变量要存点。
heap.push({ 0, 1 }); // 这个顺序不能倒,pair排序时是先根据first,再根据second,这里显然要根据距离排序
while(heap.size())
{
PII k = heap.top(); // 取不在集合S中距离最短的点
heap.pop();
int ver = k.second, distance = k.first;
if(st[ver]) continue;
st[ver] = true;
for(int i = h[ver]; i != -1; i = ne[i])
{
int j = e[i]; // i只是个下标,e中在存的是i这个下标对应的点。 j是要到达的下一个点
if(dist[j] > dist[ver] + w[i])
{
dist[j] = dist[ver] + w[i];
//i是下标 distance 是从1到当前点的距离 反而w[i]当前点到要到达j点的距离
heap.push({ dist[j], j });
}
}
}
if(dist[n] == 0x3f3f3f3f) return -1;
else return dist[n];
}
int main()
{
memset(h, -1, sizeof(h));//初始话
scanf("%d%d", &n, &m);
while (m--)
{
int x, y, c;
scanf("%d%d%d", &x, &y, &c);
add(x, y, c);
}
cout << dijkstra() << endl;
return 0;
}
bf算法 做有边数限制的最短路问题 omn
没有经过多少次的限制
枚举k次+遍历所有的边
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 510, M = 10010;
struct Edge
{
int a, b, c;
}edges[M];
int n, m, k;
int dist[N];
int last[N];
void bellman_ford()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
for (int i = 0; i < k; i ++ )//循环k次
{
memcpy(last, dist, sizeof dist);//备份防止使用本次的结果 更新
for (int j = 0; j < m; j ++ )//遍历每个边 更新最短距离
{
auto e = edges[j];
dist[e.b] = min(dist[e.b], last[e.a] + e.c);//使用上次结果共更新
//到a点的距离+本条边
}
}
}
int main()
{
scanf("%d%d%d", &n, &m, &k);
for (int i = 0; i < m; i ++ )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
edges[i] = {a, b, c};
}
bellman_ford();
if (dist[n] > 0x3f3f3f3f / 2) puts("impossible");有负边
else printf("%d\n", dist[n]);
return 0;
}
spfa on-omn
不一定更新所有的边 上面的优化
只有边更新的才放入
我们只用遍历那些到源点距离变小的点所连接的边即可,只有当一个点的前驱结点更新了,该节点才会得到更新;
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 100010;
int n, m;
int h[N], w[N], e[N], ne[N], idx;
int dist[N];
bool st[N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
int spfa()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
queue<int> q;
q.push(1);
st[1] = true;
while (q.size())
{
int t = q.front();
q.pop();
st[t] = false;//重新设置为false 以便下次更新
for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[t] + w[i])
{
dist[j] = dist[t] + w[i];//更新正常更新 放入才不是正常放入 当元素更新不了 就放不进去的时候就说明更新完了
if (!st[j])//只有不在队列中的元素才放入
{
q.push(j);
st[j] = true;
}
}
}
}
return dist[n];
}
int main()
{
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
while (m -- )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
int t = spfa();
if (t == 0x3f3f3f3f) puts("impossible");
else printf("%d\n", t);
return 0;
}
spfa 判断负环
开始把所有点放入负环 一个cnt数组表示到达这个点的边数
如果到达这个点的数量比n还多说明 存在负环 更新了狠毒欧茨
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 2010, M = 10010;
int n, m;
int h[N], w[M], e[M], ne[M], idx;
int dist[N], cnt[N];
bool st[N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
bool spfa()
{
queue<int> q;
for (int i = 1; i <= n; i ++ )
{
st[i] = true;
q.push(i);
}
while (q.size())
{
int t = q.front();
q.pop();
st[t] = false;
for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[t] + w[i])
{
dist[j] = dist[t] + w[i];
cnt[j] = cnt[t] + 1;
if (cnt[j] >= n) return true;
if (!st[j])
{
q.push(j);
st[j] = true;
}
}
}
}
return false;
}
int main()
{
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
while (m -- )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
if (spfa()) puts("Yes");
else puts("No");
return 0;
}
floyd n^3 n=200
k是n次 i是n次 j是n次
最后是从d[i][k]+d[k][j]
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 205,inf=1e9;
int n,m,k;
int d[N][N];
void floyd(){
for (int k = 1; k <= n; k ++ ){
for (int i = 1; i <= n; i ++ ){
for (int j = 1; j <= n; j ++ ){
d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
}
}
}
}
int main()
{
cin >> n>>m>>k;
for (int i = 1; i <= n; i ++ ){
for (int j = 1; j <= n; j ++ ){
if(i==j) d[i][j]=0;
else d[i][j]=inf;
}
}
for(int i=0;i<m;i++){
int a,b,c;
cin >> a>>b>>c;
d[a][b]=min(d[a][b],c);
}
floyd();
for (int i = 0; i < k; i ++ ){
int a,b;cin>>a>>b;
if(d[a][b]>inf/2) cout << "impossible";
else cout << d[a][b];
cout << endl;
}
return 0;
}
最小生成树 :联通每个点的最小距离,找所有的点离连通块最近
数据保证 肯定所有的点都联通
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 510, INF = 0x3f3f3f3f;
int n, m;
int g[N][N];
int dist[N];
bool st[N];
int prim()
{
memset(dist, 0x3f, sizeof dist);
dist[1]=0;//默认从第一个点开始找,这里如果说第一个点和其他 点都不连通 那么下面始终都会return 只不过是早点return还是晚点return的问题
int res = 0;
for (int i = 0; i < n; i ++ )
{//循环n次
int t = -1;
for (int j = 1; j <= n; j ++ )
if (!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;//选中距离最小的点t
if (dist[t] == INF) return INF;//如果在第一次选的时候 所有的点都没有更新过 所以
res += dist[t];//第一个点不妨,第一个点不放还没有联通的 概念 此时的dist[]没更新都是inf
st[t] = true;//标记为以及放入
for (int j = 1; j <= n; j ++ ) dist[j] = min(dist[j], g[t][j]);//如果每个点 从t到这个点的距离变小的话 就更新
}
return res;
}
int main()
{
scanf("%d%d", &n, &m);
memset(g, 0x3f, sizeof g);
while (m -- )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
g[a][b] = g[b][a] = min(g[a][b], c);
}
int t = prim();
if (t == INF) puts("impossible");
else printf("%d\n", t);
return 0;
}
Kruskal 克鲁斯卡尔算法
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010, M = 200010, INF = 0x3f3f3f3f;
int n, m;
int p[N];//父亲集合
struct edge
{
int a, b, w;
}edges[M];
bool cmp(struct edge A,struct edge B){
return A.w<B.w;
}
int find(int x)//并查集
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int kruskal()
{
sort(edges, edges + m,cmp);//排序边
for (int i = 1; i <= n; i ++ ) p[i] = i; // 初始化并查集
int res = 0, cnt = 0;
for (int i = 0; i < m; i ++ )
{
int a = edges[i].a, b = edges[i].b, w = edges[i].w;
a = find(a), b = find(b);
if (a != b)
{
p[a] = b;
res += w;
cnt ++ ;
}
}
if (cnt < n - 1) return INF;//处理的边数小于那么多就可以
return res;
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 0; i < m; i ++ )
{
int a, b, w;
scanf("%d%d%d", &a, &b, &w);
edges[i] = {a, b, w};
}
int t = kruskal();
if (t == INF) puts("impossible");
else printf("%d\n", t);
return 0;
}
染色法 判断二分图
二分图:当且仅当图中不含奇数环 偶数才能分一半
一个点不能既是白色又是黑色
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010, M = 200010;
int n, m;
int h[N], e[M], ne[M], idx;
int color[N];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
bool dfs(int u, int c)
{
color[u] = c;//标记
for (int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
if (!color[j])//没被染色
{
if (!dfs(j, 3 - c)) return false;//要么染色1 要么染为2
}
else if (color[j] == c) return false;//如果被染色了 看看是不是和当前颜色一样 如果一样就说明这样做不行
}
return true;
}
int main()
{
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
while (m -- )
{
int a, b;
scanf("%d%d", &a, &b);
add(a, b), add(b, a);
}
bool flag = true;
for (int i = 1; i <= n; i ++ )//所有点都尝试一遍 防止给的非联通图
if (!color[i])//如果没染过色
{
if (!dfs(i, 1))//染色为1
{
flag = false;//标记 如果
break;
}
}
if (flag) puts("Yes");
else puts("No");
return 0;
}
匈牙利算法
二分图的最大匹配https://www.acwing.com/problem/content/863/
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 510, M = 100010;
int n1, n2, m;
int h[N], e[M], ne[M], idx;
int match[N];//match对女生而言 里面的值是他的数量
bool st[N];//st表示选择这个男生 有没有为*这个女生*匹配男生了 无论能否匹配上
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
bool find(int x)
{
for (int i = h[x]; i != -1; i = ne[i])
{
int j = e[i];//j相当于女生
if (!st[j])
{
st[j] = true;//这个女生已经在main里的这一轮男生 被问过了 ,
if (match[j] == 0 || find(match[j]))//如果j没对象没匹配过 或者 女生j已经匹配的男生可以再匹配另外一个 递归
{
match[j] = x;//这个女生把x作为对象
return true;
}
}
}
return false;//对这个男生所有情况以及实现了
}
int main()
{
scanf("%d%d%d", &n1, &n2, &m);
memset(h, -1, sizeof h);
while (m -- )
{
int a, b;
scanf("%d%d", &a, &b);
add(a, b);
}
int res = 0;
for (int i = 1; i <= n1; i ++ )
{
memset(st, false, sizeof st);//对于每个男生无论女生有没有对象 对应的女生都要去询问
if (find(i)) res ++ ;//为每个男生选择对象
}
printf("%d\n", res);
return 0;
}
匈牙利最大匹配 搭档 需要魅力值相差小于等于1的人组队
因为没有边 所以要自己连接边
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 510, M = 100010;
int n1, n2, m;
int a[N],b[N];
int h[N], e[M], ne[M], idx;
int match[N];//match对女生而言 里面的值是他的数量
bool st[N];//st表示选择这个男生 有没有为*这个女生*匹配男生了 无论能否匹配上
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
bool find(int x)
{
for (int i = h[x]; ~i ; i =ne[i])
{
int j = e[i];//j相当于女生
if (!st[j])
{
st[j] = true;//这个女生已经在main里的这一轮男生 被问过了 ,
if (match[j] == 0 || find(match[j]))//如果j没对象没匹配过 或者 女生j已经匹配的男生可以再匹配另外一个 递归
{
match[j] = x;//这个女生把x作为对象
return true;
}
}
}
return false;//对这个男生所有情况以及实现了
}
int main()
{
cin >> n1;
for (int i = 1; i <= n1; i ++ ) cin >> a[i];
cin >> n2;
for (int i = 1; i <= n2; i ++ ) cin >> b[i];
sort(a+1,a+1+n1);
sort(b+1,b+1+n2);
memset(h,-1,sizeof h);
for(int i=1;i<=n1;i++){
for(int j=1;j<=n2;j++){
if(abs(a[i]-b[j] )<=1) add(i,j);
}
}
int res = 0;
for (int i = 1; i <= n1; i ++ )
{
memset(st, false, sizeof st);//对于每个男生无论女生有没有对象 对应的女生都要去询问
if (find(i)) res ++ ;//为每个男生选择对象
}
printf("%d\n", res);
return 0;
}
拓朴排序
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int N = 1e5+10;
int n,m;
int d[N];//d记录入度
int q[N];//队列处理所有入度为0的情况
int h[N],e[N],ne[N],idx;
void add(int a,int b){
e[idx]=b,ne[idx]=h[a] ,h[a]=idx++ ;
}
bool top(){
int hh=0,tt=-1;
for (int i = 1; i <= n; i ++ ){
if(d[i]==0) q[++tt]=i;
}
while(hh<=tt){
int t=q[hh++];
for (int i = h[t]; ~i; i =ne[i] ){
int t=e[i];
d[t]--;
if(!d[t]) q[++tt]=t;
}
}
return tt==n-1;//tt到了第n-1个 说明把所有点都放进过队列
}
int main()
{
cin >> n>>m;
memset( h,-1 ,sizeof h);
for (int i = 0; i < m; i ++ ){
int a,b;
cin>>a>>b;
add(a, b);
d[b]++;
}
if(top()){
for (int i = 0; i < n; i ++ ) cout << q[i]<<" ";
}
else cout << "-1"<<endl;
return 0;
}