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; } } }
ll dfn[N], low[N], ins[N], stk[N], num = 0, top = 0, scc = 0, bel[N]; vector<ll> e[N];
voidtarjan(ll u){ dfn[u] = low[u] = (++num); ins[u] = 1, stk[++top] = u; for (auto v : e[u]) { if (!dfn[v]) tarjan(v), low[u] = min(low[u], low[v]); elseif (ins[v]) low[u] = min(low[u], dfn[v]); } if (dfn[u] == low[u]) { scc++; do { ll tmp = stk[top]; bel[tmp] = scc; ins[tmp] = 0; } while (u != 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]); } } }
边双连通分量
在一张连通的无向图中,对于两个点 u 和
v,如果无论删去哪条边(只能删去一条)都不能使它们不连通,我们就说 u 和 v
边双连通。
在一张连通的无向图中,对于两个点 u 和
v,如果无论删去哪条点(只能删去一个,不能删 u 或
v)都不能使它们不连通,我们就说 u 和 v 点双连通。
ll n, m; ll dfn[N], low[N], stk[N], num = 0, top = 0, scc = 0, bel[N]; vector<PII> e[N]; vector<ll> ans[N];
voidtarjan(ll u, ll last){ dfn[u] = low[u] = (++num); stk[++top] = u; for (auto [v, id] : e[u]) { if (id == (last ^ 1)) // 防止重边现象 continue; if (!dfn[v]) tarjan(v, id), low[u] = min(low[u], low[v]); else low[u] = min(low[u], dfn[v]); } if (dfn[u] == low[u]) { scc++; do { ll tmp = stk[top]; bel[tmp] = scc; ans[scc].push_back(tmp); } while (u != stk[top--]); } }
voidsolve(){ cin >> n >> m; for (ll i = 1, u, v; i <= m; i++) { cin >> u >> v; e[u].push_back({v, i * 2}); e[v].push_back({u, i * 2 + 1}); } for (ll i = 1; i <= n; i++) { if (!dfn[i]) { tarjan(i, 0); } } cout << scc << endl; for (ll i = 1; i <= scc; i++) { cout << ans[i].size() << " "; for (auto j : ans[i]) { cout << j << " "; } cout << endl; } }
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; } }
vector<PII> e[N]; vector<ll> ans; ll n, m, cur[N], in[N], out[N], vis[N * 2], st = 0, ed = 0;
boolcmp(PII a, PII b){ if (a.first == b.first) return a.second < b.second; return a.first < b.first; }
voiddfs(ll now){ for (ll &i = cur[now]; i < e[now].size(); i++) { if (!vis[e[now][i].second]) { vis[e[now][i].second] = 1; dfs(e[now][i].first); } } ans.push_back(now); }
voidsolve(){ cin >> n >> m; for (ll i = 1, u, v; i <= m; i++) { cin >> u >> v; e[u].push_back({v, i}); in[v]++; out[u]++; } for (ll i = 1; i <= n; i++) { sort(e[i].begin(), e[i].end(), cmp); if (abs(in[i] - out[i]) > 1) { cout << "No" << endl; return; } if (out[i] - in[i] == 1) { if (st) { cout << "No" << endl; return; } st = i; } if (in[i] - out[i] == 1) { if (ed) { cout << "No" << endl; return; } ed = i; } } if (!st) { st = 1; } dfs(st); if (ans.size() != m + 1) { cout << "No" << endl; return; } for (auto i = ans.rbegin(); i != ans.rend(); ++i) { cout << *i << " "; } cout << endl; }
intmain(){ ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr); ll T; T = 1; while (T--) { solve(); } return0; }
如果以无向图为例,求是否存在欧拉回路或者欧拉通路。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
voiddfs(ll now){ for (ll &i = cur[now]; i < e[now].size(); i++) { if (!vis[e[now][i].second]) { vis[e[now][i].second] = 1; vis[e[now][i].second ^ 1] = 1; dfs(e[now][i].first); } } ans.push_back(now); }
for (ll i = 1, u, v; i <= m; i++) { cin >> u >> v; e[u].push_back({v, i * 2});//一定要注意 2 3 是一对,4 5 是一对... e[v].push_back({u, i * 2 + 1}); du[u]++; du[v]++; minn = min({minn, u, v}); }
voidsolve(){ ll ans = 0; cin >> dinicn >> m >> s >> t; for (ll i = 1, u, v, w; i <= m; i++) { cin >> u >> v >> w; e[u].push_back((edge){v, w, e[v].size()}); e[v].push_back((edge){u, 0, e[u].size() - 1}); } while (bfs()) { ans += dfs(s, inf); } cout << ans << endl; }
intmain(){ ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr); ll T; T = 1; while (T--) { solve(); } return0; }