单源最短路的建图方式

1129. 热浪

  • 思路 :单源最短路算法中除了bellmanford一般不用以外,普D为O(n2)O(n^2)O(n2),优D为O(m∗logn)O(m*logn)O(m∗logn),spfa平均是O(m)O(m)O(m)
  • 语法 :链式前向星时注意如果无向图边数开数组要*2
#include <iostream>
#include <cstring>
#include <queue>using namespace std;const int N = 2510, M = 6210 * 2;typedef pair<int, int> pii;int h[N], e[M], ne[M], w[M], idx;
int dist[N];
bool st[N];
int n, m, S, T;void add(int a, int b, int c)
{e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}void dijkstra()
{memset(dist, 0x3f, sizeof dist);dist[S] = 0;priority_queue<pii, vector<pii>, greater<pii>> heap;heap.push({0, S});while (heap.size()){auto t = heap.top();heap.pop();int ver = t.second, distance = t.first;if (st[ver]) continue;st[ver] = true;for (int i = h[ver]; i != -1; i = ne[i]){int j = e[i];if (dist[j] > dist[ver] + w[i]){dist[j] = dist[ver] + w[i];heap.push({dist[j], j});}}}
}int main()
{memset(h, -1, sizeof h);cin >> n >> m >> S >> T;for (int i = 0; i < m; i ++ ){int a, b, c;cin >> a >> b >> c;add(a, b, c);add(b, a, c);}dijkstra();cout << dist[T];return 0;
}
  • spfa写法 :注意因为spfa中每个点可能被入队多次,所以总入队次数可能会非常大,可能数组越界,所以要用循环队列,注意循环队列的写法。一般用spfa算法不太会被卡,卡了再换
#include <iostream>
#include <cstring>using namespace std;const int N = 2510, M = 6200 * 2 + 10;      // Mint h[N], e[M], ne[M], w[M], idx;
int dist[N];
int q[N];       // 循环队列大小为N
bool st[N];
int n, m, S, T;void add(int a, int b, int c)
{e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}void spfa()
{memset(dist, 0x3f, sizeof dist);dist[S] = 0;int hh = 0, tt = 1;     // 与普通队列不一样q[0] = S;st[S] = true;     // 在队列内 == st为truewhile (hh != tt)        // 与普通队列的判空条件不同{int t = q[hh ++ ];if (hh == N) hh = 0;       // 循环队列st[t] = false;      // 出队for (int i = h[t]; ~i; i = ne[i]){int j = e[i];if (dist[j] > dist[t] + w[i]){dist[j] = dist[t] + w[i];if (!st[j])     // 如果松弛更新后不在队列内就放入{q[tt ++ ] = j;if (tt == N) tt = 0;     // 循环队列st[j] = true;}}}}
}int main()
{cin >> n >> m >> S >> T;memset(h, -1, sizeof h);while (m -- ){int a, b, c;cin >> a >> b >> c;add(a, b, c), add(b, a, c);}spfa();cout << dist[T];return 0;
}

1128. 信使

  • 思路 :核心 :对于每个点来说,它接收到信的时间,等于它到指挥部的最短距离;那么问题转化为从第一个点到其他所有点的最短路径中的最大值,如果有一个点与1号点的距离是正无穷,说明不连通,输出-1。可以用floyd,且它短。O(n3)O(n^3)O(n3)。
  • 注意点 :注意邻接矩阵写法初始化一定要有0的部分
  • 画图分析 :
#include <iostream>
#include <cstring>using namespace std;const int N = 110, inf = 0x3f3f3f3f;int d[N][N];int main()
{int n, m;cin >> n >> m;memset(d, 0x3f, sizeof d);for (int i = 1; i <= n; i ++ ) d[i][i] = 0;     // important!while (m -- ){int i, j, k;cin >> i >> j >> k;d[i][j] = d[j][i] = min(d[i][j], k);}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 res = 0;for (int i = 1; i <= n; i ++ )if (d[1][i] == inf){res = -1;break;}else res = max(res, d[1][i]);cout << res;return 0;
}

1127. 香甜的黄油

#include <iostream>
#include <cstring>using namespace std;const int N = 810, M = 1450 * 2 + 10, inf = 0x3f3f3f3f;int h[N], e[M], ne[M], w[M], idx;
int dist[N];
int cow[N];
int q[N];
bool st[N];
int n, p, c;void add(int a, int b, int d)
{e[idx] = b, w[idx] = d, ne[idx] = h[a], h[a] = idx ++ ;
}int spfa(int u)
{memset(dist, 0x3f, sizeof dist);dist[u] = 0;int hh = 0, tt = 1;q[0] = u;st[u] = true;while (hh != tt){int t = q[hh ++ ];if (hh == N) hh = 0;st[t] = false;for (int i = h[t]; ~i; i = ne[i]){int j = e[i];if (dist[j] > dist[t] + w[i]){dist[j] = dist[t] + w[i];if (!st[j]){q[tt ++ ] = j;if (tt == N) tt = 0;st[j] = true;}}}}int res = 0;for (int i = 1; i <= n; i ++ ){if (dist[cow[i]] == inf) return inf;res += dist[cow[i]];}return res;
}int main()
{memset(h, -1, sizeof h);cin >> n >> p >> c;for (int i = 1; i <= n; i ++ ) cin >> cow[i];for (int i = 0; i < c; i ++ ){int a, b, d;cin >> a >> b >> d;add(a, b, d), add(b, a, d);}int res = inf;for (int i = 1; i <= p; i ++ )res = min(res, spfa(i));cout << res << endl;return 0;
}

1126. 最小花费

#include <iostream>
#include <cstring>using namespace std;const int N = 2e3 + 10;double g[N][N];
double dist[N];
bool st[N];
int n, m, S, T;void dijkstra()
{dist[S] = 1;for (int i = 0; i < n; i ++ ){int t = -1;for (int j = 1; j <= n; j ++ )if (!st[j] && (t == -1 || dist[t] < dist[j]))t = j;st[t] = true;for (int j = 1; j <= n; j ++ )dist[j] = max(dist[j], dist[t] * g[t][j]);}
}int main()
{scanf("%d%d", &n, &m);for (int i = 0; i < m; i ++ ){int a, b, c;scanf("%d%d%d", &a, &b, &c);double z = (100 - c) / 100.0;g[a][b] = g[b][a] = max(g[a][b], z);}scanf("%d%d", &S, &T);dijkstra();printf("%.8lf\n", 100 / dist[T]);return 0;
}

920. 最优乘车

#include <iostream>
#include <cstring>
#include <sstream>using namespace std;const int N = 510;int stop[N];
bool g[N][N];
int q[N];
int dist[N];
int n, m;void bfs()
{memset(dist, 0x3f, sizeof dist);int hh = 0, tt = 0;q[0] = 1;dist[1] = 0;while (hh <= tt){int t = q[hh ++ ];for (int i = 1; i <= n; i ++ )if (g[t][i] && dist[i] > dist[t] + 1){dist[i] = dist[t] + 1;q[ ++ tt] = i;}}
}int main()
{cin >> m >> n;getchar();while (m -- ){string line;getline(cin, line);stringstream ssin(line);int cnt = 0, p;while (ssin >> p) stop[cnt ++ ] = p;for (int i = 0; i < cnt; i ++ )for (int j = i + 1; j < cnt; j ++ )g[stop[i]][stop[j]] = true;}bfs();if (dist[n] == 0x3f3f3f3f) cout << "NO" << endl;else cout << max(dist[n] - 1, 0) << endl;return 0;
}

903. 昂贵的聘礼


  • 等价于求s到1的最短路径
#include <iostream>
#include <cstring>using namespace std;const int N = 110, inf = 0x3f3f3f3f;int w[N][N];
int dist[N];
int level[N];
bool st[N];
int n, m;int dijkstra(int down, int up)
{memset(dist, 0x3f, sizeof dist);memset(st, 0, sizeof st);dist[0] = 0;for (int i = 0; i < n; i ++ )       // n + 1个点{int t = -1;for (int j = 0; j <= n; j ++ )if (!st[j] && (t == -1 || dist[j] < dist[t]))t = j;st[t] = true;for (int j = 1; j <= n; j ++ )if (level[j] <= up && level[j] >= down)       // 每次只走等级在这个区间内的点dist[j] = min(dist[j], dist[t] + w[t][j]);}return dist[1];
}int main()
{cin >> m >> n;memset(w, 0x3f, sizeof w);for (int i = 0; i <= n; i ++ ) w[i][i] = 0;for (int i = 1; i <= n; i ++ ){int price, cnt;cin >> price >> level[i] >> cnt;w[0][i] = min(w[0][i], price);while (cnt -- ){int id, cost;cin >> id >> cost;w[id][i] = min(w[id][i], cost);}}// 最后答案是所有区间求出来的最小值取一个min,100 * 10000int res = inf;for (int i = level[1] - m; i <= level[1]; i ++ ) res = min(res, dijkstra(i, i + m));cout << res;return 0;
}

单源最短路的综合应用

1135. 新年好


  • 注意spfa是有常数的,所以6e7不好过
  • 这样的话时间复杂度从改变顺序前爆搜的5! * 5 * 10^5,变成了现在的6*O(m) + 5!
  • 这道题后来发现spfa会T
#include <iostream>
#include <cstring>
#include <queue>using namespace std;const int N = 5e4 + 10, M = 2e5 + 10, inf = 0x3f3f3f3f;      // 双向边typedef pair<int, int> pii;int n, m;
int source[6];
int dist[6][N];     // 二维数组
bool st[N];
int h[N], e[M], ne[M], w[M], idx;int ans = inf;void add(int a, int b, int c)
{e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}void dijkstra(int start, int dist[])
{memset(dist, 0x3f, sizeof(int) * N);       // 注意更新dist的方式,因为它是一个二维数组memset(st, 0, sizeof st);       // st也要每次更新priority_queue<pii, vector<pii>, greater<pii>> heap;dist[start] = 0;heap.push({0, start});while (heap.size()){auto t = heap.top();heap.pop();int ver = t.second, distance = t.first;if (st[ver]) continue;     // 只要更新就可以放进去,拿出来则是要看是否stst[ver] = true;for (int i = h[ver]; ~i; i = ne[i]){int j = e[i];if (dist[j] > dist[ver] + w[i]){dist[j] = dist[ver] + w[i];heap.push({dist[j], j});}}}
}// 现在是选第几个点,这次的起点,已经有的距离
void dfs(int u, int start, int distance)
{if (distance >= ans) return ;      // 最优性剪枝if (u == 6){ans = distance;return ;}// 从五个source中选for (int i = 1; i <= 5; i ++ )if (!st[i]){st[i] = true;dfs(u + 1, i, distance + dist[start][source[i]]);st[i] = false;      // 恢复现场}
}int main()
{memset(h, -1, sizeof h);scanf("%d%d", &n, &m);source[0] = 1;for (int i = 1; i <= 5; i ++ ) scanf("%d", &source[i]);while (m -- ){int x, y, t;scanf("%d%d%d", &x, &y, &t);add(x, y, t), add(y, x, t);}for (int i = 0; i <= 5; i ++ ) dijkstra(source[i], dist[i]);memset(st, 0, sizeof st);dfs(1, 0, 0);       // 开始放第一个点printf("%d\n", ans);return 0;
}

340. 通信线路

  • 双向 -> 无向
  • 路径权重变为第k + 1大值,如果边的数量不足k条,就变成0
  • 题目描述到有k条边可以免费升级,因此只需求1~N的所有路径中第k + 1大的值的最小值,是最大最小值模型,因此可以用二分求解
  • 对于区间[0,1000001]中的某一个点x :
  • 1.check(x)函数表示 :从1走到N,最少经过的长度大于x的边数的数量是否小于等于k,若是则返回true,否则false。这个性质可以把我们的这个区间分成两个部分,使得答案左边是满足这个性质,答案右边是不满足这个性质(二分特点)。对于答案来说,这个数如果可以作为答案,一定存在一条从1到N的路径上x是第k+1大数,也就是说这个路径上只存在k个比x大的数,满足性质(从1走到N,最少经过的长度大于x的边数的数量是否小于等于k)。
  • 2.求出从1走到N最少经过的长度大于x的边数的数量,可以分为 :
  • 如果边大于x,边权看作1;否则为0
  • 初始l = 0,r = 1000001的原因是:如果1号点到n号点是不连通的,最后二分出来的值一定是1000001,表示无解,而0是可能的
  • 对于只有两种边权是0,1可以使用双端队列BFS求解
#include <iostream>
#include <cstring>
#include <queue>using namespace std;const int N = 1e3 + 10, M = 2e4 + 10;typedef pair<int, int> pii;int n, p, k;
int h[N], e[M], ne[M], w[M], idx;
int dist[N];
bool st[N];void add(int a, int b, int l)
{e[idx] = b, w[idx] = l, ne[idx] = h[a], h[a] = idx ++ ;
}bool check(int bound)
{memset(dist, 0x3f, sizeof dist);memset(st, 0, sizeof st);dist[1] = 0;priority_queue<pii, vector<pii>, greater<pii>> heap;heap.push({0, 1});while (heap.size()){auto t = heap.top();heap.pop();int ver = t.second, distance = t.first;if (st[ver]) continue;st[ver] = true;for (int i = h[ver]; ~i; i = ne[i]){int j = e[i];if (dist[j] > dist[ver] + (w[i] > bound)){dist[j] = dist[ver] + (w[i] > bound);heap.push({dist[j], j});}}}if (dist[n] <= k) return true;return false;
}int main()
{cin >> n >> p >> k;memset(h, -1,sizeof h);while (p -- ){int a, b, l;cin >> a >> b >> l;add(a, b, l), add(b, a, l);}int l = 0, r = 1000001;while (l < r){int mid = (l + r) >> 1;if (check(mid)) r = mid;else l = mid + 1;}if (r == 1000001) cout << "-1" << endl;else cout << r << endl;return 0;
}

单源最短路的扩展应用

1137. 选择最佳线路

  • 虚拟源点的方法:只能求出来的是多个起点到任意终点的一条最短距离(计算出多个起点到终点最短的一条),而不能求出多个起点每个起点到任意终点的最短路距离(后者是floyd算法)
#include <iostream>
#include <cstring>using namespace std;const int N = 1e3 + 10, M = 2e4 + 10, inf = 0x3f3f3f3f;int h[N], e[M], ne[M], w[M], idx;
int dist[N], q[N];
bool st[N];
int n, m, T;void add(int a, int b, int t)
{e[idx] = b, w[idx] = t, ne[idx] = h[a], h[a] = idx ++ ;
}void spfa()
{memset(dist, 0x3f, sizeof dist);
//    memset(st, 0, sizeof st);int scnt;cin >> scnt;int hh = 0, tt = 0;while (scnt -- ){int u;cin >> u;dist[u] = 0;q[tt ++ ] = u;st[u] = true;}while (hh != tt){int t = q[hh ++ ];if (hh == N) hh = 0;st[t] = false;for (int i = h[t]; ~i; i = ne[i]){int j = e[i];if (dist[j] > dist[t] + w[i]){dist[j] = dist[t] + w[i];if (!st[j]){q[tt ++ ] = j;if (tt == N) tt = 0;st[j] = true;}}}}
}int main()
{while (scanf("%d%d%d", &n, &m, &T) != -1){memset(h, -1, sizeof h);idx = 0;while (m -- ){int a, b, t;cin >> a >> b >> t;add(a, b, t);}spfa();if (dist[T] == inf) cout << -1 << endl;else cout << dist[T] << endl;}return 0;
}

1134. 最短路计数

  • 要求最短路计数首先满足条件是不能存在值为0的环,因为存在的话那么被更新的点的条数就inf了,要把图抽象成一种最短路树(拓扑图)。
  • 求最短路的算法 :
  • BFS只入队一次,出队一次,可以抽象成拓扑图。因为它可以保证被更新的点的父节点一定已经是最短距离了,并且这个点的条数已经被完全更新过了,这个性质是核心性质。
  • dijkstra每个点只出队一次,可以抽象成拓扑图。同理由于每一个出队的点一定已经是最短距离,并且它出队的时候是队列中距离最小的点,这就代表它的最短距离条数已经被完全更新了,所以构成拓扑性质。
  • spfa每个点会进队出队多次,不满足拓扑序
  • bellman-ford和spfa本身不具备拓扑序,因为更新它的点不一定是最短距离,所以会出错,举个例子 :
  • 时间复杂度 :如果用BFS做,每个点只会入队出队一次,时间复杂度是O(n)O(n)O(n),如果用堆优化版dijkstra做,时间复杂度为O(mlog(n))O(mlog(n))O(mlog(n))
#include <iostream>
#include <cstring>using namespace std;const int N = 1e5 + 10, M = 4e5 + 10, mod = 100003;int n, m;
int h[N], e[M], ne[M], idx;
int dist[N], cnt[N], q[N];void add(int a, int b)
{e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}void bfs()
{memset(dist, 0x3f, sizeof dist);dist[1] = 0, cnt[1] = 1;int hh = 0, tt = 0;q[0] = 1;while (hh <= tt){int t = q[hh ++ ];for (int i = h[t]; ~i; i = ne[i]){int j = e[i];if (dist[j] > dist[t] + 1){dist[j] = dist[t] + 1;cnt[j] = cnt[t];q[ ++ tt] = j;     // BFS可以保证被更新的点的父节点一定已经是最短距离了,并且它出队的时候是队列中距离最小的点,所以被更新了的点直接放入队列中即可,甚至不需要st数组,也能保证BFS中每个点仅被入队出队一次}else if (dist[j] == dist[t] + 1){cnt[j] = (cnt[j] + cnt[t]) % mod;}}}
}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);}bfs();for (int i = 1; i <= n; i ++ ) cout << cnt[i] << endl;return 0;
}

Floyd算法

1125. 牛的旅行

#include <iostream>
#include <cstring>
#include <math.h>#define x first
#define y secondusing namespace std;typedef pair<double, double> pdd;const int N = 155;
const double inf = 1e20;            // 赋给double数组的inf开成double,可以开1e20int n;
char g[N][N];       // 0,1 char
pdd q[N];       // 坐标
double d[N][N];
double maxd[N];double get_dist(int i, int j)
{double dx = q[i].x - q[j].x, dy = q[i].y - q[j].y;return sqrt(dx * dx + dy * dy);                          // math.h
}int main()
{cin >> n;for (int i = 0; i < n; i ++ ) cin >> q[i].x >> q[i].y;for (int i = 0; i < n; i ++ ) cin >> g[i];          // 01连在一起,所以用char装,自然分开// 由是否有直接的边相连和坐标计算边权。同一个点,有直接的边相连,没有。// 虽然是无向图,但是是邻接矩阵,不用d[i][j] = d[j][i]for (int i = 0; i < n; i ++ )for (int j = 0; j < n; j ++ )if (i == j) d[i][j] = 0;else if (g[i][j] == '1') d[i][j] = get_dist(i, j);else d[i][j] = inf;// 由边权计算多源最短距离for (int k = 0; k < n; k ++ )for (int i = 0; i < n; i ++ )for (int j = 0; j < n; j ++ )d[i][j] = min(d[i][j], d[i][k] + d[k][j]);double r1 = 0;for (int i = 0; i < n; i ++ ){for (int j = 0; j < n; j ++ )if (d[i][j] < inf / 2)      // 如果i和j是连通的。即找与i连通的点中最远的maxd[i] = max(maxd[i], d[i][j]);r1 = max(maxd[i], r1);}double r2 = inf;for (int i = 0; i < n; i ++ )for (int j = 0; j < n; j ++ )if (d[i][j] > inf / 2)      // 如果i和j原本是不连通的r2 = min(r2, maxd[i] + maxd[j] + get_dist(i, j));printf("%.6lf\n", max(r1, r2));return 0;
}

343. 排序

  • floyd决传递闭包问题,核心思想:i --> k且k --> j,则i – > j
  • 注意:题目说到从前往后遍历每对关系,每次遍历时判断,因此每一次新增一关系都得做floyd和check()判断
#include <iostream>
#include <cstring>using namespace std;const int N = 30;int n, m;
bool g[N][N], d[N][N];
bool st[N];void floyd()
{memcpy(d, g, sizeof g);         // cstring, floyd用两个数组存for (int k = 0; k < n; k ++ )for (int i = 0; i < n; i ++ )for (int j = 0; j < n; j ++ )if (d[i][k] && d[k][j])d[i][j] = 1;        // 判断点i能否通过中间点k到达j
}int check()
{for (int i = 0; i < n; i ++ )if (d[i][i])                    // 形成连通环,从i能返回ireturn 1;       // 矛盾for (int i = 0; i < n; i ++ )for (int j = 0; j < i; j ++ )       // wa,注意这里j枚举到i - 1,这样d[i][j]这个值不会重复,且不会枚举到j == iif (!d[i][j] && !d[j][i])return 0;       // 未明确return 2;       // 已确认
}void get_min()
{for (int i = 0; i < n; i ++ )if (!st[i]){bool success = true;for (int j = 0; j < n; j ++ )if (d[j][i] && !st[j])      // 如果有排在i前面且还没有被输出过的点{success = false;break;}if (success){cout << (char)('A' + i);st[i] = true;}}
}int main()
{while (cin >> n >> m, n || m){memset(g, 0, sizeof g);int type = 0, t;for (int i = 1; i <= m; i ++ ){string s;cin >> s;if (!type)      // 如果type已经不是0了,那只管输入即可,不需要判断了{int a = s[0] - 'A', b = s[2] - 'A';g[a][b] = 1;floyd();type = check();if (type) t = i;}}if (!type) puts("Sorted sequence cannot be determined.");else if (type == 1) printf("Inconsistency found after %d relations.\n", t);else{printf("Sorted sequence determined after %d relations: ", t);memset(st, 0, sizeof st);for (int i = 0; i < n; i ++ ) get_min();cout << '.' << endl;}}return 0;
}

最小生成树

1140. 最短网络

#include <iostream>
#include <cstring>using namespace std;const int N = 110;int n;
int g[N][N];
int dist[N];
bool st[N];int prim()
{int res = 0;memset(dist, 0x3f, sizeof dist);dist[1] = 0;for (int i = 0; i < n; i ++ ){int t = -1;for (int j = 0; j < n; j ++ )if (!st[j] && (t == -1 || dist[j] < dist[t]))t = j;st[t] = true;if (i) res += dist[t];for (int j = 0; j < n; j ++ )dist[j] = min(dist[j], g[t][j]);}return res;
}int main()
{cin >> n;for (int i = 0; i < n; i ++ )for (int j = 0; j < n; j ++ )cin >> g[i][j];cout << prim() << endl;return 0;
}
  • 如果这道题用kruskal做,输入的时候要有if(i>j)的判断,防止重复建边

1141. 局域网

#include <iostream>
#include <algorithm>using namespace std;const int N = 110, M = 210;int n, m;
struct Edge
{int a, b, w;bool operator< (const Edge &W) const{return w < W.w;}
}e[M];
int fa[N];int find(int x)
{if (fa[x] != x) fa[x] = find(fa[x]);return fa[x];
}int main()
{cin >> n >> m;for (int i = 1; i <= n; i ++ ) fa[i] = i;for (int i = 0; i < m; i ++ ){int a, b, w;cin >> a >> b >> w;e[i] = {a, b, w};}sort(e, e + m);int res = 0;for (int i = 0; i < m; i ++ ){int a = find(e[i].a), b = find(e[i].b), w = e[i].w;if (a != b) fa[a] = b;else res += w;}cout << res << endl;return 0;
}

最小生成树的扩展应用

1146. 新的开始

  • 为了供应电力,要么在当前位置i建发电站,要么与另外的已有电力供应的矿井j之间建立电网
  • 1.在当前位置i建发电站的费用是viv_ivi​,建立虚拟结点S,相当于i点到S点的费用是viv_ivi​
  • 2.如图所示,求n个矿井电力供应的最小花费,等价于求n + 1个点的最小生成树
  • O(n2)O(n^2)O(n2)
#include <iostream>
#include <cstring>using namespace std;const int N = 310;int n;
int w[N][N];
int dist[N];
bool st[N];int prim()
{memset(dist, 0x3f, sizeof dist);dist[0] = 0;                           // 虚拟结点int res = 0;for (int i = 0; i < n + 1; i ++ )           // n + 1个点{int t = -1;for (int j = 0; j < n + 1; j ++ )if (!st[j] && (t == -1 || dist[t] > dist[j]))t = j;st[t] = true;if (i) res += dist[t];for (int j = 0; j < n + 1; j ++ )dist[j] = min(dist[j], w[t][j]);}return res;
}int main()
{scanf("%d", &n);for (int i = 1; i <= n; i ++ ){scanf("%d", &w[0][i]);w[i][0] = w[0][i];                 // 无向图 <- 最小生成树}for (int i = 1; i <= n; i ++ )for (int j = 1; j <= n; j ++ )scanf("%d", &w[i][j]);          // “对称的”邻接矩阵的输入方式自带无向图属性,也就是MST算法的条件cout << prim() << endl;return 0;
}

负环

904. 虫洞

#include <iostream>
#include <cstring>using namespace std;const int N = 510, M = 5210;        // 2500 * 2 + 200 + 10int n, m1, m2;
int h[N], ne[M], e[M], w[M], idx;
int dist[N];            // dist表示1到x的最短距离,cnt表示1到x的最短路的边数
int q[N], cnt[N];
bool st[N];void add(int a, int b, int t)
{e[idx] = b, w[idx] = t, ne[idx] = h[a], h[a] = idx ++ ;
}bool spfa()
{memset(dist, 0, sizeof dist);           // 初始化为0而不是infmemset(cnt, 0, sizeof cnt);memset(st, 0, sizeof st);int hh = 0, tt = 0;for (int i = 1; i <= n; i ++ ){q[tt ++ ] = i;         // 初始把所有点都放入队列,不止放1,因为1可能到不了有负环的点st[i] = true;}while (hh != tt){int t = q[hh ++ ];if (hh == N) hh = 0;st[t] = false;for (int i = h[t]; ~i; 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[tt ++ ] = j;if (tt == N) tt = 0;st[j] = true;}}}}return false;
}int main()
{int T;scanf("%d", &T);while (T -- ){scanf("%d%d%d", &n, &m1, &m2);// 图的初始化memset(h, -1, sizeof h);idx = 0;for (int i = 0; i < m1; i ++ ){int a, b, t;scanf("%d%d%d", &a, &b, &t);add(a, b, t), add(b, a, t);     // 这里是无向的,也就是双向}for (int i = 0; i < m2; i ++ ){int a, b, t;scanf("%d%d%d", &a, &b, &t);add(a, b, -t);              // 这里是单向的}if (spfa()) puts("YES");else puts("NO");}
}

差分约束

1169. 糖果



#include <iostream>
#include <cstring>using namespace std;const int N = 1e5 + 10, M = 3e5 + 10;typedef long long ll;int n, m;
int h[N], e[M], ne[M], w[M], idx;
ll dist[N];
int cnt[N], q[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 ++ ;
}// 这道题判负环的时候会TLE
// 上一次的“负环”题目中的trick方法还是太玄学了
// 这里用一个不会TLE的判负环方法,那就是把SPFA算法中的循环队列改为栈
// 这样对于遇到的负环,就不会加入队尾,直到再次遍历完整个队列才去算它
// 遇到负环会直接在栈顶连续入栈出栈,直到判断它的cnt[i] >= n + 1,即发现负环
bool spfa()
{// 因为是求所有x_i的最小值,因此就是求不等式的下界的最大值// 转而就是求图论的最长路memset(dist, -0x3f, sizeof dist);     // 求最长路时,初始化为-0x3fdist[0] = 0;      // 虚拟源点cnt[0] = 0;int hh = 0, tt = 1;q[0] = 0;while (hh != tt){int t = q[ -- tt];st[t] = false;      // 出栈,即st数组表示元素是否在栈内for (int i = h[t]; ~i; 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 + 1) return false;          // n + 1个点(算上超级源点),所以变数 >= n + 1才有负环if (!st[j]){q[tt ++ ] = j;st[j] = true;}}}}return true;
}int main()
{scanf("%d%d", &n, &m);memset(h, -1, sizeof h);while (m -- ){int x, a, b;scanf("%d%d%d", &x, &a, &b);if (x == 1) add(a, b, 0), add(b, a, 0);else if (x == 2) add(a, b, 1);else if (x == 3) add(b, a, 0);else if (x == 4) add(b, a, 1);else add(a, b, 0);}// 建立一个能到达所有点的虚拟源点0for (int i = 1; i <= n; i ++ ) add(0, i, 1);     // x_0 <= x_i + 1if (!spfa()) puts("-1");else{ll res = 0;for (int i = 1; i <= n; i ++ ) res += dist[i];cout << res << endl;}return 0;
}

最近公共祖先

1172. 祖孙询问

  • 最近公共祖先的定义 :
  • 向上标记法 & 倍增法 :
  • 2^15=32768,所以log(4e4)约等于15,0-15就是16
  • 宽搜比深搜有一个好处,就是不会爆栈,更保险
#include <iostream>
#include <cstring>using namespace std;const int N = 4e4 + 10, M = N * 2;      // 无向图int n, m;
int h[N], e[M], ne[M], idx;
int depth[N], fa[N][16];
int q[N];void add(int a, int b)
{e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}void bfs(int root)
{memset(depth, 0x3f, sizeof depth);depth[0] = 0;               // 哨兵depth[root] = 1;int hh = 0, tt = 0;q[0] = root;// bfs队列while (hh <= tt){int t = q[hh ++ ];for (int i = h[t]; ~i; i = ne[i]){int j = e[i];if (depth[j] > depth[t] + 1){depth[j] = depth[t] + 1;q[ ++ tt] = j;              // 更新了深度,就放进队列,因为宽搜// 发展fa数组fa[j][0] = t;for (int k = 1; k <= 15; k ++ )fa[j][k] = fa[fa[j][k - 1]][k - 1];}}}
}int lca(int a, int b)
{if (depth[a] < depth[b]) swap(a, b);        // 保证a是深度更深的for (int k = 15; k >= 0; k -- )         // 先跳更高if (depth[fa[a][k]] >= depth[b])        // 如果a跳2^k和b一样高或者比b低,a就继续跳a = fa[a][k];// 上面这个判断显示出了哨兵的好处,如果a跳k跳出了根结点,fa[a][k]相当于会返回0,然后depth[0]会返回0// 树中任何一个点的depth都是大于等于0的,那么跳出去了的话,depth[0]就一定会小于depth[b],不成立if (a == b) return a;       // 现在的a或者b就是公共祖先// 否则a和b同时往上跳for (int k = 15; k >= 0; k -- )if (fa[a][k] != fa[b][k]){a = fa[a][k];b = fa[b][k];}return fa[a][0];
}int main()
{scanf("%d", &n);int root = 0;memset(h, -1, sizeof h);for (int i = 0; i < n; i ++ ){int a, b;scanf("%d%d", &a, &b);if (b == -1) root = a;else add(a, b), add(b, a);}bfs(root);scanf("%d", &m);while (m -- ){int a, b;scanf("%d%d", &a, &b);int p = lca(a, b);if (p == a) puts("1");else if (p == b) puts("2");else puts("0");}return 0;
}

有向图的强连通分量

1174.受欢迎的牛

  • 被除自己之外的所有牛认为受欢迎 -> 能被所有点走到当前这个点;只要在反图上从这个点出发遍历所有点,整个复杂度就是n∗(n+m)n * (n + m)n∗(n+m)
  • 那如果这是一个拓扑图DAG有向无环图,如果存在两个终点(也就是出度为0的点),这两个点之间是无法相互到达的,也就是说不可能有一个点被所有受欢迎,所以说,存在至少两个出度为0的点的话,答案就是0;如果只存在一个出度为0的点,答案就是1
  • 如果这个图不是拓扑图怎么办呢,就利用强连通分量将它转换成拓扑图,然后所有点之后就会变成一个有向无环图。最后就是看出度为0的点(只有一个,否则答案为0)的强连通分量里有多少个点,里边的所有点相互之间可以到达,而分量外的点都可以走到分离内的点,所以这个分量内的所有点都可以被其它所有点走到的,答案就是分量里点的数量;
  • 当一个强连通的出度为0,则该强连通分量中的所有点都被其他强连通分量的牛欢迎;但假如存在两及以上个出度=0的牛(强连通分量) 则必然有一头牛(强连通分量)不被所有牛欢迎
  • 求出的是缩点之后的出度为零的点,因为在这张图上任意点都能到达“终点”,而且tarjan缩点中任意两点可以互相到达,等价于从所有点都能走到终点。

tarjan强连通分量算法


#include <iostream>
#include <cstring>using namespace std;const int N = 1e4 + 10, M = 5e4 + 10;int n, m;
int h[N], ne[M], e[M], idx;
int dfn[N], low[N], timestamp;
int stk[N], top;
bool in_stk[N];
int id[N], scc_cnt, Size[N];
int dout[N];void add(int a, int b)
{e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}void tarjan(int u)
{dfn[u] = low[u] = ++ timestamp;         // u的时间戳stk[ ++ top] = u, in_stk[u] = true;      // 入栈for (int i = h[u]; ~i; i = ne[i]){int j = e[i];if (!dfn[j])                    // j点未被遍历过{tarjan(j);      // 所以逆dfs序low[u] = min(low[u], low[j]);    // 之所以用j的low而不是dfn就是因为j可能到了更高层,那么是用low更新的// j也许存在反向边到达比u还高的层,所以用j能到的最小dfn序(最高点)更新u能达到的(最小dfn序)最高点}else if (in_stk[j]) low[u] = min(low[u], dfn[j]);  // 之所以用j的dfn而不是low是因为j比u还早在栈内// 如果j还在栈中,说明还没有出栈,是dfn序比当前点u小的// 则其 1 要么是横插边(左边分支的点)//         o//        / \//       j ← u//     2 要么是u的祖宗节点//         j//      ↗///       u//    两种情况u的dfs序都比j大 所以用dfn[j]更新low[u]// 栈代表当前未被搜完的强连通分量的所有点}// tarjan完是逆dfs序// 假设这里是最高的根节点fa// 上面几行中 fa的儿子节点j都已经在它们的递归中走完了下面9行代码// 其中就包括 ++scc_cnt// 即递归回溯到高层节点的时候 子节点的scc都求完了// 节点越高 scc_id越大// 在我们后面想求链路dp的时候又得从更高层往下// 所以得for(int i=scc_cnt(根节点所在的scc);i;i--)开始// 所以当遍历完u的所有能到的点后 发现u最高能到的点是自己// 1 则u为强连通分量中的最高点,则以u为起点往下把该强连通分量所有节点都找出来// 2 要么它就没有环,就是一个正常的往下的点if (dfn[u] == low[u]) // 当前强连通分量里的最后一个点{++ scc_cnt;int y;// 出栈并标记所在强连通分量,以及记录该强连通分量点数// 之所以用do while而不用while,假如当前第一个y正好为udo {y = stk[top -- ];in_stk[y] = false;id[y] = scc_cnt;Size[scc_cnt] ++ ;} while (y != u);//1 因为栈中越高的元素的dfs序越大,那么我们只需要把dfs序比u大的这些pop到u//即因为最终会从下至上回到u 所以当y==u//则说明点u所在的所有强连通分量都标记了id//           →  u//          /  ///         /  ne1//         ← ne2//      因为ne2会在u能到的dfs序里最大的,也就是此时的栈顶//      那么我们就逐一pop出ne2和ne1//2 要么它就是一个没有环的点 则该点单点成一个连通分量}
}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);}for (int i = 1; i <= n; i ++ )if (!dfn[i])        // 如果未遍历tarjan(i);// 统计强连通分量出度for (int i = 1; i <= n; i ++ )for (int j = h[i]; ~j; j = ne[j]){int k = e[j];int a = id[i], b = id[k];if (a != b) dout[a] ++ ;        // 如果不在一个强连通分量内,这个强连通分量出度+1}int zeros = 0, sum = 0;for (int i = 1; i <= scc_cnt; i ++ )if (!dout[i])       // 强连通分量出度为0{zeros ++ ;sum += Size[i];if (zeros > 1){sum = 0;break;}}printf("%d", sum);return 0;
}

无向图的双连通分量

395. 冗余路径

  • 什么是割点与桥
  • tarjan割点算法核心思想






#include <iostream>
#include <cstring>using namespace std;const int N = 5010, M = 2e4 + 10;int n, m;
int h[N], ne[M], e[M], idx;
int dfn[N], low[N], timestamp;
int stk[N], top;
int id[N], dcc_cnt;
bool is_bridge[M];
int d[N];void add(int a, int b)
{e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}void tarjan(int u, int from)
{dfn[u] = low[u] = ++ timestamp;stk[ ++ top] = u;for (int i = h[u]; ~i; i = ne[i]){int j = e[i];if (!dfn[j])        // j未被遍历过{tarjan(j, i);low[u] = min(low[u], low[j]);if (dfn[u] < low[j])        // j到不了u{// 则 x - y的边是桥// 正向边is_bridge[i]和反向边is_bridge[i ^ 1]都是桥is_bridge[i] = is_bridge[i ^ 1] = true;// 这里i如果是奇数,则反向边 = i - 1 = i ^ 1//          偶  ,则反向边 = i + 1 = i ^ 1}}else if (i != (from ^ 1))   // j遍历过且i不是反向边(即i不是指向u父节点的边){                           // 因为我们不能用u的父节点的时间戳更新ulow[u] = min(low[u], dfn[j]);}}// 双连通分量起点uif (dfn[u] == low[u]){dcc_cnt ++ ;int y;do {y = stk[top -- ];id[y] = dcc_cnt;} while (y != u);}
}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);}tarjan(1, -1);      // 防止搜反向边,用一个from// 如果边i是桥,在其所连的出边的点j所在强连通分量的度+1// 桥两边的双连通分量各+1for (int i = 0; i < idx; i ++ )if (is_bridge[i])d[id[e[i]]] ++ ;int cnt = 0;for (int i = 1; i <= dcc_cnt; i ++ )if (d[i] == 1)          // 多少个度数为1的节点(强连通分量),叶子结点cnt ++ ;cout << (cnt + 1) / 2 << endl;return 0;
}

二分图

257.关押罪犯

#include <iostream>
#include <cstring>using namespace std;const int N = 2e4 + 10, M = 2e5 + 10;int n, m;
int h[N], ne[M], e[M], w[M], idx;
int color[N];void add(int a, int b, int c)
{e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}bool dfs(int u, int c, int mid)
{color[u] = c;           // 首先先对这个点进行染色for (int i = h[u]; ~i; i = ne[i]){int j = e[i];if (w[i] <= mid) continue;if (color[j]){if (color[j] == c) return false;}else if (!dfs(j, 3 - c, mid)) return false;     // 是else if}return true;
}bool check(int mid)
{memset(color, 0, sizeof color);for (int i = 1; i <= n; i ++ )if (!color[i])if (!dfs(i, 1, mid))return false;return true;
}int main()
{cin >> n >> m;memset(h, -1, sizeof h);while (m -- ){int a, b, c;cin >> a >> b >> c;add(a, b, c), add(b, a, c);}int l = 0, r = 1e9;while (l < r){int mid = (l + r) >> 1;if (check(mid)) r = mid;    // 因为要尽可能小,注意不要和上图中证明二分可行性弄混了else l = mid + 1;}cout << r << endl;return 0;
}

欧拉回路和欧拉路径

1123. 铲雪车

  • 因为每条边都是双向边,对某一条边而言,一条从a到b使出度+1,另一条从b到a使入度+1,所以这个图是很特殊的图,所有点的入度和出度都是相等的,因此这个图必然存在欧拉回路,所以这个车不管从哪个点开始,必然可以每条边不重复,只遍历一次的再回到当前起点
  • 所以最短时间就是所有边长度的2倍

欧拉回路 : 终点就是起点
一、连通的无向图
1 存在欧拉路径的充要条件 : 度数为奇数的点只能有0或2个
2 存在欧拉回路的充要条件 : 度数为奇数的点只能有0个
二、连通的有向图
1 存在欧拉路径的充要条件 :
要么所有点的出度均 = 入度;
要么除了两个点之外,其余所有点的出度 = 入度 剩余的两个点:一个满足出度 - 入度 = 1(起点) 一个满足入度 - 出度 = 1(终点)
2 存在欧拉回路的充要条件 : 所有点的出度均等于入度

  • 本题 :
  • 每条路是双向道 -> 每条边都要被铲两次
  • 双向道 :每个点的入度 == 出度
  • <->
  • 必然存在欧拉回路
  • <->
  • 必然存在一笔画法
  • 最短时间 :正好一笔画完所有双向边
#include <iostream>
#include <math.h>using namespace std;int main()
{double x1, y1, x2, y2;cin >> x1 >> y1;double sum = 0;while (cin >> x1 >> y1 >> x2 >> y2){double dx = x2 - x1;double dy = y2 - y1;sum += sqrt(dx * dx + dy * dy) * 2;}int minutes = round(sum / 1000 / 20 * 60);     // round四舍五入,<math.h>int hours = minutes / 60;minutes %= 60;printf("%d:%02d\n", hours, minutes);return 0;
}

拓扑排序

1191. 家谱树

#include <iostream>
#include <cstring>using namespace std;const int N = 110, M = N * N / 2;       // 树,有向,的最多边数int n;
int h[N], ne[M], e[M], idx;
int d[N], q[N];void add(int a, int b)
{e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}// q数组来实现队列,且队列内就是拓扑排序结果
void topsort()
{int hh = 0, tt = -1;for (int i = 1; i <= n; i ++ )if (!d[i])q[ ++ tt] = i;while (hh <= tt){int t = q[hh ++ ];for (int i = h[t]; ~i; i = ne[i]){int j = e[i];if ( -- d[j] == 0){q[ ++ tt] = j;}}}
}int main()
{memset(h, -1, sizeof h);cin >> n;for (int i = 1; i <= n; i ++ ){int son;while (cin >> son, son){add(i, son);d[son] ++ ;}}topsort();for (int i = 0; i < n; i ++ ) cout << q[i] << ' ';return 0;
}

AcWing算法提高课 Level-3 第三章 图论相关推荐

  1. ACWing算法提高课 友好城市

    ACWing算法提高课 友好城市 Palmia国有一条横贯东西的大河,河有笔直的南北两岸,岸上各有位置各不相同的N个城市. 北岸的每个城市有且仅有一个友好城市在南岸,而且不同城市的友好城市不相同. 每 ...

  2. AcWing算法提高课-3.1.2信使

    宣传一下算法提高课整理 <- CSDN个人主页:更好的阅读体验 <- 题目传送门点这里 题目描述 战争时期,前线有 n n n 个哨所,每个哨所可能会与其他若干个哨所之间有通信联系. 信使 ...

  3. 算法——AcWing算法提高课中代码和题解

    文章目录 第一章 动态规划 (完成情况:64/68) 数字三角形模型 最长上升子序列模型 背包模型 状态机模型 状态压缩DP 区间DP 树形DP 数位DP 单调队列优化DP 斜率优化DP 第二章 搜索 ...

  4. AcWing算法提高课-3.1.1热浪

    宣传一下算法提高课整理 <- CSDN个人主页:更好的阅读体验 <- 题目传送门点这里 题目描述 德克萨斯纯朴的民众们这个夏天正在遭受巨大的热浪!!! 他们的德克萨斯长角牛吃起来不错,可是 ...

  5. [AcWing算法提高课]之图论 单源最短路的综合应用(C++题解)

    目录 1)热浪(板子题) (朴素dijkstra) O(n2) (堆优化dijkstra) O((n+m)logm) (spfa) O(m) 2)信使 3)香甜的黄油 4)最小花费 5) 最优乘车 6 ...

  6. AcWing算法提高课笔记

    目录 Level2 1.动态规划--从集合角度考虑DP问题 1.1 数字三角形模型 1.1.1摘花生 1.1.2最低通行费 1.1.3方格取数 1.1.4传纸条 1.2 最长上升子序列模型 1.2.1 ...

  7. AcWing算法提高课 Level-3 第二章 搜索

    池塘计数 题目 提交记录 讨论 题解 视频讲解 农夫约翰有一片 N∗M 的矩形土地. 最近,由于降雨的原因,部分土地被水淹没了. 现在用一个字符矩阵来表示他的土地. 每个单元格内,如果包含雨水,则用& ...

  8. 数字三角形,最长上升子序列,背包模型 AcWing算法提高课 (详解)

    目录 数字三角形模型(只能向右和向下或向左和向上) AcWing 1015. 摘花生 AcWing 1018. 最低通行费(曼哈顿距离-向右和向下-求最小值-初始化) AcWing 1027. 方格取 ...

  9. AcWing算法提高课

    1. 动态规划(43/68) 1.1 数字三角形模型(4/4) 1.1.1 AcWing 1015. 摘花生 结论: f[i][j]=max⁡(f[i−1][j],f[i][j−1])+w[i][j] ...

最新文章

  1. 9-分析事物问题并编写 Utils 文件
  2. 每日一博 - Java序列化一二事儿
  3. PHP处理图片(orientation)旋转问题
  4. 故障分析--主从复制故障1
  5. Supermemo背单词7周年纪念
  6. 去掉字符串中的单引号和双引号_同时搞定Android和iOS的Dart语言(4):字符串类型...
  7. LeetCode 494. 目标和(DFS+DP)
  8. java多线程之生产者和消费者问题
  9. 字符串匹配问题(信息学奥赛一本通-T1355)
  10. JavaScript函数作用域
  11. 苹果支付招聘业务开发经理 需有加密货币支付工作经验
  12. 拓端tecdat|python在Scikit-learn中用决策树和随机森林预测NBA获胜者
  13. 7 个优秀 WordPress LMS 在线教育系统插件比较(优点和缺点)
  14. 如何让网页字体文件大瘦身?前端字体优化知多D
  15. 奥克兰大学计算机科学专业学费,奥克兰大学各专业学费
  16. 北工大计算机学院博导,北工大计算机学院计算机科学与技术导师介绍:段立娟...
  17. 深度学习入门---PCA,白化
  18. echo “c“ > /proc/sysrq-trigger 让linux系统崩溃之后的恢复办法
  19. PS CS6视频剪辑基本技巧(二)视频剪接和添加图片
  20. C语言运算符的优先级和结合

热门文章

  1. HANA数据库为何如此之快
  2. SAP 用户权限解析
  3. POJO和javabean的异同
  4. SAP修改已经释放的请求
  5. SAP标准成本估算删除
  6. SAP 财务会计结构
  7. ABAP--SAP是如何回写CL_GUI_ALV_GRID_BASE的MT_MODIFIED_CELLS的
  8. abap range 或 Filter的说明
  9. Nginx变身爆火神器,手把手教你在永洪BI中应用
  10. android ndk jni so,Android Studio Ndk So 文件