voidfloyd(int n){ for (int k = 1; k <= n; k++) { for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { if (e[i][j] > e[i][k] + e[k][j]) e[i][j] = e[i][k] + e[k][j]; } } } return; }
SPFA 算法(慎用)
可用于负权图。时间复杂度可被卡到 \(O(nm)\)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
queue<int> q;
q.push(x); for (int i = 1; i <= n; i++) dis[i] = inf; dis[x] = 0; vis[x] = 1; while (!q.empty()) { int now = q.front(); q.pop(); vis[now] = 0; for (int i = head[now]; i; i = e[i].next) { int to = e[i].end; if (dis[to] > dis[now] + e[i].len) { dis[to] = dis[now] + e[i].len; if (!vis[to]) q.push(to), vis[to] = 1; } } }
dfn [N] //时间戳。 low [N] //一个节点向子树方向能够追溯到的最早的时间戳。 ins [N] //是否在栈中 stk [N] //手工栈。注意,这个栈与系统栈不同。 bel [N] //一个节点从属于哪个强连通分量。 top = 0//栈的顶部指针。 scc = 0//强连通分量个数。 index = 0//用来戳时间戳。
voidtarjan(int now) { dfn[now] = low[now] = (++index); //戳时间戳 ins[now] = 1; stk[++top] = now; //打上入栈标记,入栈 for (int i = head[now]; i; i = e[i].nxt) { int to = e[i].end; if (!dfn[to]) tarjan(to), low[now] = min(low[now], low[to]); //进行dfs,并根据前文求low_u。 elseif (ins[to]) low[now] = min(low[now], dfn[to]); //已经在栈中且访问过了,回溯,并根据前文求low_u。 } if (dfn[now] == low[now]) //强连通分量增加了 { scc++; do { int tmp = stk[top]; ins[tmp] = 0; bel[tmp] = scc; } while (now != stk[top--]); //弹栈 } } //注意,一遍tarjan不一定遍历所有点,所以要tarjan所有未访问的点。用dfn[]判断是否访问。
缩点
求完强连通分量后缩点可转化为有向无环图。
1 2 3 4 5 6 7 8 9 10
voidbuild(){ for (int now = 1; now <= n; now++) { for (int j = big.head[now]; j; j = big.e[j].nxt) { int to = big.e[j].end; if (bel[now] == bel[to]) continue; small.add(bel[now], bel[to]); } } }
int g[N][N], v[N], d[N]; //边,访问标记,距离当前已加入的点集的距离 voidprim(){ for (int i = 1; i <= n; i++) d[i] = g[1][i]; v[1] = 1; for (int i = 1; i <= n - 1; i++) {//寻找 n-1 轮 int to = -1, mn = inf; for (int j = 1; j <= n; j++) if (!v[j] && d[j] < mn) to = j, mn = d[j]; if (to < 0) return; ans += mn, v[to] = 1;//成为最小生成树的树边 for (int j = 1; j <= n; j++) if (!v[j] && d[j] > g[to][j]) d[j] = g[to][j]; } return; }
Kruskal 算法
从小到大加入边,是个贪心算法。需要并查集维护。
最小瓶颈生成树即对于图 G
中的生成树上最大的边权值在所有生成树中最小。最小生成树是它的子集。
最小瓶颈路问题是指在一张无向图中,询问一个点对 \((u,v)\),需要找出从 u 到 v
的一条简单路径,使路径上所有边中边权最大值最小。最小生成树上 u 到 v
的路径一定是 u 到 v 的最小瓶颈路之一。
1 2 3 4 5 6 7 8 9 10 11 12 13
voidkruskal(){ int num = 0, ru, rv; sort(e + 1, e + cnt + 1, cmp); for (int i = 1; i <= m; i++) { ru = ufds.find(e[i].u), rv = ufds.find(e[i].v); if (ru == rv) continue; ans += e[i].len;//成为最小生成树的树边 ufds.merge(ru, rv); if (++num == n - 1) break; } }
usingnamespace std; #define PII pair<int,int> #define TIII tuple<int,int,int> #define int long long //网络流(dinic)
constint N = 205, M = 5005; vector<TIII > e[M]; //点,边权,反向边编号 int lev[N], cur[N], ans; //bfs分层,弧优化,最大流 int n, m, s, t;
intbfs(){ for (int i = 0; i <= n; i++)lev[i] = -1, cur[i] = 0; queue<int> q; q.push(s); lev[s] = 1; while (!q.empty()) { int x = q.front(); q.pop(); for (auto [y, w, temp2]: e[x]) { if (lev[y] == -1 && w > 0) { lev[y] = lev[x] + 1; q.push(y); } } } return lev[t] != -1; }
intdfs(int x, int flow){ if (x == t) { return flow; } for (int &i = cur[x]; i < e[x].size(); i++) { auto &[y, w, rev] = e[x][i]; if (lev[y] == lev[x] + 1 && w > 0) { int lim = dfs(y, min(flow, w)); if (lim > 0) { w -= lim; std::get<1>(e[y][rev]) += lim; return lim; } } } return0; }
signedmain(){ ios::sync_with_stdio(false); cin.tie(0), cout.tie(0); cin >> n >> m >> s >> t; for (int i = 0; i < m; i++) { int x, y, w; cin >> x >> y >> w; e[x].push_back({y, w, e[y].size()}); e[y].push_back({x, 0, e[x].size() - 1}); } while (bfs()) { while (1) { int x = dfs(s, 4e18); if (x == 0)break; ans += x; } } cout << ans; return0; }