当前位置:   article > 正文

图论中的模板

图论中的模板

有向图的拓扑排序

  1. //有向图无环图中才有拓扑排序,且都是前面的编号的点指向后面编号的点
  2. #include<iostream>
  3. #include<cstring>
  4. using namespace std;
  5. const int N = 1e5 + 9;
  6. int e[N], ne[N], h[N], idx, n, m, d[N], q[N];
  7. void add(int a, int b)
  8. {
  9. e[idx] = b, ne[idx] = h[a], h[a] = idx++;
  10. }
  11. bool topsort()
  12. {
  13. int hh = 0, tt = -1;
  14. //将所有入度为0的点入队,编号从1~n
  15. for (int i = 1; i <= n; ++i) if (!d[i]) q[++tt] = i;
  16. while (hh <= tt)
  17. {
  18. int t = q[hh++];//出队
  19. for (int i = h[t]; i != -1; i = ne[i])
  20. {
  21. int j = e[i];
  22. d[j]--;//因为t指向j,又因为t删除了,所以j入度减一
  23. if (!d[j]) q[++tt] = j;
  24. }
  25. }
  26. return tt == (n - 1);//如果每个点都入队了表明为拓扑排序
  27. }
  28. int main()
  29. {
  30. ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
  31. memset(h, -1, sizeof h);
  32. cin >> n >> m;
  33. for (int i = 0; i < m; ++i)
  34. {
  35. int a, b; cin >> a >> b;
  36. add(a, b);
  37. d[b]++;//入度加一
  38. }
  39. //队列中的顺序刚好就是拓扑排序,排序不唯一
  40. if (topsort()) for (int i = 0; i < n; ++i) cout << q[i] << " ";
  41. else cout << -1;
  42. return 0;
  43. }

dijkstra求最短路

  1. //dijkstra堆优化
  2. #include<iostream>
  3. #include<cstring>
  4. #include<queue>
  5. using namespace std;
  6. const int N = 1e5 + 9;
  7. typedef pair<int, int> PII;
  8. int h[N], e[N], ne[N], idx, n, m, dist[N], w[N];
  9. bool st[N];
  10. void add(int x, int y, int z)
  11. {
  12. e[idx] = y, w[idx] = z, ne[idx] = h[x], h[x] = idx++;
  13. }
  14. int dijkstra()
  15. {
  16. memset(dist, 0x3f, sizeof dist);
  17. dist[1] = 0;
  18. //first是距离。second是编号
  19. //小根堆
  20. //有更改就加入
  21. //可能加入的点会有冗余,比如1号点一个是10,一个是15,所以用一个st[]数组来标记
  22. priority_queue<PII, vector<PII>, greater<PII>> heap;
  23. heap.push({ 0, 1 });
  24. while (heap.size())
  25. {
  26. //每次取出距离最小的点
  27. auto t = heap.top();
  28. heap.pop();
  29. int ver = t.second, distance = t.first;
  30. //不更新也对,不过会有很多冗余情况考虑
  31. //同一个顶点x,可能在队列中有(dis1, x)、(dis2, x)存在
  32. //而dis1 < dis2,所以(dis1, x)先出队并更新x的邻接顶点
  33. //当(dis2, x)出队的时候,由于dis2 > dis1
  34. //所以x的邻接顶点此时都不会更新,那么(dis2, x)这一步也就冗余了
  35. if (st[ver]) continue;//如果距离已经确定,则跳过该点
  36. st[ver] = true;
  37. for (int i = h[ver]; i != -1; i = ne[i])
  38. {
  39. int j = e[i];
  40. if (dist[j] > distance + w[i])
  41. {
  42. dist[j] = distance + w[i];
  43. heap.push({ dist[j], j });
  44. }
  45. }
  46. }
  47. if (dist[n] == 0x3f3f3f3f) return -1;
  48. return dist[n];
  49. }
  50. int main()
  51. {
  52. ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
  53. memset(h, -1, sizeof h);
  54. cin >> n >> m;
  55. while (m--)
  56. {
  57. int x, y, z; cin >> x >> y >> z;
  58. add(x, y, z);
  59. }
  60. cout << dijkstra();
  61. return 0;
  62. }

有边数限制的最短路

  1. //bellman_ford()算法
  2. //dijkstra()不能解决负权边最短路问题
  3. #include<iostream>
  4. #include<cstring>
  5. using namespace std;
  6. const int N = 510, M = 1e4 + 9;
  7. //back[]存储上一次的dist[]
  8. int dist[N], back[N], n, m, k;
  9. struct Edge
  10. {
  11. int a, b, w;
  12. } edges[M];
  13. int bellman_ford()
  14. {
  15. memset(dist, 0x3f, sizeof dist);
  16. dist[1] = 0;
  17. //存在边数限制,只能用bellman_ford做
  18. //关于为什么循环k次可以保证得到的dist就是相应k条件下的值?
  19. //当明白了备份的作用,就能理解了
  20. //备份的意义就是保证了每条边a->b在每次刷新时a都是独立的
  21. //在遍历过程中dist不会受其他点的影响,只和a上一次的值有关
  22. //而这个备份的操作就是放在k循环下的,所以得到的dist就是相应k条件下的值
  23. for (int i = 0; i < k; ++i)
  24. {
  25. //做备份
  26. //防止发生串联
  27. //串联:由于这个算法的特性决定,
  28. //每次更新得到的必然是在多考虑 1 条边之后能得到的全局的最短路
  29. //而串联指的是一次更新之后考虑了不止一条边:由于使用了松弛
  30. //某节点的当前最短路依赖于其所有入度的节点的最短路
  31. //假如在代码中使用dist[e.b]=min(dist[e.b],dist[e.a] + e.c)
  32. //我们无法保证dist[e.a]是否也在本次循环中被更新,如果被更新了
  33. //并且dist[e.b] > dist[e.a] + e.c
  34. //那么会造成当前节点在事实上“即考虑了一条从某个节点指向a的边
  35. //也考虑了a->b”,共两条边
  36. //而使用dist[e.b]=min(dist[e.b],last[e.a] + e.c)
  37. //可以保证a在dist更新后不影响对b的判定,因为后者使用last数组
  38. //保存着上一次循环中的dist的值
  39. memcpy(back, dist, sizeof dist);
  40. for (int j = 0; j < m; ++j)
  41. {
  42. int a = edges[j].a, b = edges[j].b, w = edges[j].w;
  43. dist[b] = min(dist[b], back[a] + w);
  44. }
  45. }
  46. //因为可能存在到n的路径中有负权边存在, 所以导致dist[n]的值小于0x3f3f3f3f
  47. //根据题目给的数据范围最多减去500*10000,也就是500w的值,所以大于一半就好
  48. //注意不要返回-1,因为-1可能就是dist[n]的距离
  49. if (dist[n] > 0x3f3f3f3f >> 1) return -0x3f3f3f3f;
  50. return dist[n];
  51. }
  52. int main()
  53. {
  54. ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
  55. cin >> n >> m >> k;
  56. for (int i = 0; i < m; ++i)
  57. {
  58. int x, y, z; cin >> x >> y >> z;
  59. edges[i].a = x, edges[i].b = y, edges[i].w = z;
  60. }
  61. int t = bellman_ford();
  62. if (t == -0x3f3f3f3f) cout << "impossible";
  63. else cout << dist[n];
  64. return 0;
  65. }

spfa求最短路

  1. //spfa
  2. #include<iostream>
  3. #include<cstring>
  4. #include<queue>
  5. using namespace std;
  6. const int N = 1e5 + 9;
  7. int h[N], e[N], ne[N], st[N], n, m, idx, w[N], dist[N];
  8. void add(int a, int b, int c)
  9. {
  10. e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
  11. }
  12. int spfa()
  13. {
  14. memset(dist, 0x3f, sizeof dist);
  15. dist[1] = 0;
  16. queue<int> q;//存储待更新点
  17. q.push(1);
  18. st[1] = true;
  19. while (q.size())
  20. {
  21. auto t = q.front();
  22. q.pop();
  23. st[t] = false;
  24. for (int i = h[t]; i != -1; i = ne[i])
  25. {
  26. int j = e[i];
  27. if (dist[j] > dist[t] + w[i])
  28. {
  29. dist[j] = dist[t] + w[i];
  30. if (!st[j])
  31. {
  32. q.push(j);
  33. st[j] = true;
  34. }
  35. }
  36. }
  37. }
  38. if (dist[n] == 0x3f3f3f3f) return -0x3f3f3f3f;
  39. return dist[n];
  40. }
  41. int main()
  42. {
  43. ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
  44. memset(h, -1, sizeof h);
  45. cin >> n >> m;
  46. while (m--)
  47. {
  48. int x, y, z; cin >> x >> y >> z;
  49. add(x, y, z);
  50. }
  51. int t = spfa();
  52. if (t == -0x3f3f3f3f) cout << "impossible";
  53. else cout << t;
  54. return 0;
  55. }

spfa判负环

  1. //spfa判断负环
  2. #include<iostream>
  3. #include<queue>
  4. #include<cstring>
  5. using namespace std;
  6. const int N = 1e4 + 9;
  7. //cnt[]存储点最短路径的边数
  8. int e[N], ne[N], h[N], w[N], dist[N], cnt[N], idx, n, m;
  9. void add(int a, int b, int c)
  10. {
  11. e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
  12. }
  13. bool spfa()
  14. {
  15. memset(dist, 0x3f, sizeof dist);
  16. queue<int> q;
  17. //注意这里是讲所有点都入队,因为负环可能不在1号点这个路径上
  18. //这里dist初不初始化都行,但要是求单点到单点路径上是否存在回路,必须初始化dist
  19. //且dist[s] = 0
  20. for (int i = 1; i <= n; ++i) q.push(i);
  21. while (q.size())
  22. {
  23. auto t = q.front();
  24. q.pop();
  25. for (int i = h[t]; i != -1; i = ne[i])
  26. {
  27. int j = e[i];
  28. if (dist[j] > dist[t] + w[i])
  29. {
  30. dist[j] = dist[t] + w[i];
  31. cnt[j] = cnt[t] + 1;
  32. //边数 >= n,点数 >= n + 1,存在回路
  33. if (cnt[j] >= n) return true;
  34. q.push(j);
  35. }
  36. }
  37. }
  38. return false;
  39. }
  40. int main()
  41. {
  42. ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
  43. memset(h, -1, sizeof h);
  44. cin >> n >> m;
  45. while (m--)
  46. {
  47. int x, y, z; cin >> x >> y >> z;
  48. add(x, y, z);
  49. }
  50. if (spfa()) cout << "Yes";
  51. else cout << "No";
  52. return 0;
  53. }

多源汇最短路问题-具有多个源点:Floyd求最短路

  1. //Floyd求多源汇最短路
  2. #include<iostream>
  3. #include<cstring>
  4. using namespace std;
  5. const int N = 210, INF = 1e9;
  6. int n, m, Q, d[N][N];
  7. void floyd()
  8. {
  9. for (int k = 1; k <= n; ++k)
  10. {
  11. for (int i = 1; i <= n; ++i)
  12. {
  13. for (int j = 1; j <= n; ++j)
  14. {
  15. d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
  16. }
  17. }
  18. }
  19. }
  20. int main()
  21. {
  22. ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
  23. cin >> n >> m >> Q;
  24. for (int i = 1; i <= n; ++i)
  25. {
  26. for (int j = 1; j <= n; ++j)
  27. {
  28. if (i == j) d[i][j] = 0;
  29. else d[i][j] = INF;
  30. }
  31. }
  32. while (m--)
  33. {
  34. int a, b, z; cin >> a >> b >> z;
  35. d[a][b] = min(d[a][b], z);//选一个最短的重边
  36. }
  37. floyd();
  38. while (Q--)
  39. {
  40. int a, b; cin >> a >> b;
  41. //因为存在负权边,所以ab之间的距离可能小于无穷
  42. if (d[a][b] > INF / 2) cout << "impossible" << '\n';
  43. else cout << d[a][b] << '\n';
  44. }
  45. return 0;
  46. }

Kruskal算法求最小生成树

  1. //kruskal求最小生成树
  2. #include<iostream>
  3. #include<algorithm>
  4. using namespace std;
  5. const int N = 2e5 + 9;
  6. struct Edge
  7. {
  8. int a, b, w;
  9. bool operator< (const Edge& W) const
  10. {
  11. return w < W.w;
  12. }
  13. } edges[N];
  14. int n, m, p[N], res, cnt;
  15. int find(int x)
  16. {
  17. if (p[x] != x) p[x] = find(p[x]);
  18. return p[x];
  19. }
  20. int main()
  21. {
  22. ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
  23. cin >> n >> m;
  24. for (int i = 0; i < m; ++i)
  25. {
  26. int a, b, w; cin >> a >> b >> w;
  27. edges[i] = { a, b, w };
  28. }
  29. //从小到大排序
  30. sort(edges, edges + m);
  31. //并查集数组初始化
  32. for (int i = 1; i <= n; ++i) p[i] = i;
  33. //如果这个边与之前选择的所有边不会组成回路,就选择这条边分;反之,舍去。
  34. //判断是否会产生回路的方法为:使用并查集。
  35. //每次将未加入的边加入到集合中去
  36. for (int i = 0; i < m; ++i)
  37. {
  38. int a = edges[i].a, b = edges[i].b, w = edges[i].w;
  39. //不在一个集合里面
  40. a = find(a), b = find(b);
  41. if (a != b)
  42. {
  43. res += w;
  44. cnt++;
  45. p[a] = b;//加入集合
  46. }
  47. }
  48. //如果集合中的边数小于n - 1,说明不存在最小生成树
  49. if (cnt < n - 1) cout << "impossible";
  50. else cout << res;
  51. return 0;
  52. }

判定是否是二分图

  1. #include<iostream>
  2. #include<cstring>
  3. using namespace std;
  4. const int N = 100010;
  5. int f[N];
  6. int e[N*2],h[N],ne[N*2],idx;
  7. //数组模拟邻接表模板
  8. void add(int a,int b)
  9. {
  10. e[idx]=b,ne[idx]=h[a],h[a]=idx++;
  11. }
  12. //并查集find函数模板:找到祖宗节点
  13. int find(int x)
  14. {
  15. if(f[x]!=x) f[x]=find(f[x]);
  16. return f[x];
  17. }
  18. int main()
  19. {
  20. int n,m;
  21. scanf("%d%d",&n,&m);
  22. memset(h,-1,sizeof h);
  23. //并查集初始化
  24. for(int i=1;i<=n;i++) f[i]=i;
  25. while(m--)
  26. {
  27. int a,b;
  28. scanf("%d%d",&a,&b);
  29. add(a,b);
  30. add(b,a);
  31. }
  32. //遍历每一个点
  33. for(int u=1;u<=n;u++)
  34. {
  35. //遍历当前点的邻接点
  36. for(int i=h[u];~i;i=ne[i])
  37. {
  38. int j=e[i];
  39. //如果邻接点和当前节点属于同一集合,不满足二分,直接NO
  40. if(find(j)==find(u))
  41. {
  42. puts("No");
  43. return 0;
  44. }
  45. //将所有邻接点合并到同一个集合
  46. f[find(e[h[u]])]=find(j);
  47. }
  48. }
  49. puts("Yes");
  50. return 0;
  51. }

二分图的最大匹配

  1. //二分图的最大匹配
  2. #include<iostream>
  3. #include<cstring>
  4. using namespace std;
  5. const int N = 510, M = 1e5 + 9;
  6. int h[N], e[M], ne[M], n1, n2, match[N], m, idx, res;
  7. bool st[N];
  8. void add(int a, int b)
  9. {
  10. e[idx] = b, ne[idx] = h[a], h[a] = idx++;
  11. }
  12. bool find(int x)
  13. {
  14. for (int i = h[x]; i != -1; i = ne[i])
  15. {
  16. int j = e[i];
  17. //没被遍历过
  18. if (!st[j])
  19. {
  20. //标记为匹配过
  21. st[j] = true;
  22. //如果这个姑娘还没有被匹配过,或者找到这个男生喜欢别的姑娘
  23. if (match[j] == 0 || find(match[j]))
  24. {
  25. match[j] = x;
  26. return true;
  27. }
  28. }
  29. }
  30. return false;
  31. }
  32. int main()
  33. {
  34. ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
  35. memset(h, -1, sizeof h);
  36. cin >> n1 >> n2 >> m;
  37. while (m--)
  38. {
  39. int a, b; cin >> a >> b;
  40. //不需要存双向边
  41. add(a, b);
  42. }
  43. for (int i = 1; i <= n1; ++i)
  44. {
  45. //每次将姑娘全初始化为没有遍历,因为之前别人喜欢的姑娘我也可能喜欢
  46. memset(st, false, sizeof st);
  47. if (find(i)) res++;//如果找到数量就加一
  48. }
  49. cout << res;
  50. return 0;
  51. }
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/天景科技苑/article/detail/842435
推荐阅读
相关标签
  

闽ICP备14008679号