1.$tarjan$求强连通分量
思想:在$dfs$的过程中,把强连通分量中的点入栈,当找到一个强连通分量的最起始的点,就将其所在强连通分量中的点出栈。
缩点
把强连通分量中的点缩成一个点,进行重新建图,从而解决一些问题。
2.割点
若将这个点在图中所连的边删去,图变得不连通,则称这个点为一个割点。
考虑两种情况:
若节点$x$为根节点,则它若联结着两颗及以上数量的子树,则$x$为割点。
$otherwise$,设$x$的其中一个儿子为$v$,若出现$low[v] ge dfn[x]$,则$x$为割点。
3.习题
luogu 3388 【模板】割点(割顶)
#include <bits/stdc++.h> const int MAXN = 20050; const int MAXM = 100050; using namespace std; struct node { int to, nextt; }edge[MAXM << 1]; int n, m, u, v, num, cnt, low[MAXN], dfn[MAXN], head[MAXN]; set<int> s; void addedge(int u, int v) { edge[++num].to = v; edge[num].nextt = head[u]; head[u] = num; } void tarjan(int x, int fa) { dfn[x] = low[x] = ++cnt; int child = 0; for(int i = head[x]; i; i = edge[i].nextt) { int to = edge[i].to; if(!dfn[to]) { if(x == fa) child++; tarjan(to, x); low[x] = min(low[x], low[to]); if(x != fa && low[to] >= dfn[x]) s.insert(x); } low[x] = min(low[x], dfn[to]); } if(x == fa && child >= 2) s.insert(x); } int main() { cin >> n >> m; for(int i = 1; i <= m; i++) { cin >> u >> v; addedge(u, v); addedge(v, u); } for(int i = 1; i <= n; i++) { if(!dfn[i]) tarjan(i, i); } cout << (int)s.size() << endl; for(set<int>::iterator it = s.begin(); it != s.end(); it++) cout << *it << " "; cout << endl; return 0; }
Luogu 2921 Trick or Treat on the Farm
第一种做法是进行$tarjan$,对于所在强连通分量里个数大于$1$的点,答案即为当前强连通分量的大小;由于数据的特殊性,另一种情况即为到强连通分量的距离加上那个强连通分量的大小。
第二种做法是三遍递归。第一遍把所有不在环内的点打上标记,第二遍求出未被标记的点所在环的大小,第三遍累加到环的距离。总体想法与第一种是一样的。
代码(#2):
#include <bits/stdc++.h> const int MAXN = 100050; using namespace std; int n, x, nextt[MAXN], ans[MAXN], in[MAXN]; bool vis[MAXN]; void dfs1(int x) { vis[x] = true; if(!--in[nextt[x]]) dfs1(nextt[x]); } int dfs2(int x, int num) { vis[x] = true; ans[x] = num; if(ans[nextt[x]]) return num; return ans[x] = dfs2(nextt[x], num + 1); } int dfs3(int x) { if(ans[x]) return ans[x]; return ans[x] = dfs3(nextt[x]) + 1; } int main() { cin >> n; for(int i = 1; i <= n; i++) { cin >> nextt[i]; ++in[nextt[i]]; } for(int i = 1; i <= n; i++) { if(!vis[i] && !in[i]) dfs1(i); } for(int i = 1; i <= n; i++) { if(!ans[i] && in[i]) x = dfs2(i, 1); } for(int i = 1; i <= n; i++) { if(!ans[i] && !in[i]) x = dfs3(i); } for(int i = 1; i <= n; i++) cout << ans[i] << endl; return 0; }
Luogu 1726 上白泽慧音
显然答案即为最大的强连通分量的个数。
#include <bits/stdc++.h> const int MAXN = 5050; const int MAXM = 50050; const int INF = 1 << 30; using namespace std; stack<int> s; struct Edge { int to , nextt; }edge[MAXM << 1]; int n, m, u, v, ty, num, cnt, cnt1, Max, pos, ss[MAXN], head[MAXN], low[MAXN], dfn[MAXN], sccno[MAXN]; void addedge(int u, int v) { edge[++num].to = v; edge[num].nextt = head[u]; head[u] = num; } void tarjan(int x) { dfn[x] = low[x] = ++cnt; s.push(x); for(int i = head[x]; i; i = edge[i].nextt) { int to = edge[i].to; if(!dfn[to]) { tarjan(to); low[x] = min(low[x], low[to]); } else if(!sccno[to]) low[x] = min(low[x], dfn[to]); } if(dfn[x] == low[x]) { int now, cnt2 = 0; ++cnt1; while(true) { now = s.top(); s.pop(); ++cnt2; sccno[now] = cnt1; ss[now] = cnt1; if(now == x) break; } if(cnt2 > Max) { Max = cnt2; pos = cnt1; } } } int main() { cin >> n >> m; for(int i = 1; i <= m; i++) { cin >> u >> v >> ty; addedge(u, v); if(ty == 2) addedge(v, u); } for(int i = 1; i <= n; i++) { if(!dfn[i]) tarjan(i); } cout << Max << endl; for(int i = 1; i <= n; i++) { if(ss[i] == pos) { for(int j = i; j <= n; j++) { if(ss[j] == pos) cout << j << " "; } return 0; } } return 0; }
Luogu 3387 【模板】缩点
先进行缩点,后作类似拓扑的操作,$dp$即可。
#include <bits/stdc++.h> const int MAXN = 10050; const int MAXM = 100050; using namespace std; stack<int> ss; queue<int> q; struct Edge1 { int u, to, nextt; }edge1[MAXM]; struct Edge2 { int u, to, nextt; }edge2[MAXN]; int n, m, u, v, ans, num1, num2, cnt, cnt1, head1[MAXN], head2[MAXN], dfn[MAXN], low[MAXN], sccno[MAXN], sd[MAXN], in[MAXN], val[MAXN], dis[MAXN]; void addedge1(int u, int v) { edge1[++num1].u = u; edge1[num1].to = v; edge1[num1].nextt = head1[u]; head1[u] = num1; } void addedge2(int u, int v) { edge2[++num2].u = u; edge2[num2].to = v; edge2[num2].nextt = head2[u]; head2[u] = num2; } void tarjan(int x) { dfn[x] = low[x] = ++cnt; ss.push(x); for(int i = head1[x]; i; i = edge1[i].nextt) { int to = edge1[i].to; if(!dfn[to]) { tarjan(to); low[x] = min(low[x], low[to]); } else if(!sccno[to]) low[x] = min(low[x], dfn[to]); } if(dfn[x] == low[x]) { ++cnt1; while(true) { int now = ss.top(); ss.pop(); sccno[now] = cnt1; sd[now] = x; if(now == x) break; val[x] += val[now]; } } } void topo() { for(int i = 1; i <= n; i++) { if(!in[i] && sd[i] == i) { q.push(i); dis[i] = val[i]; } } while(!q.empty()) { int now = q.front(); q.pop(); for(int i = head2[now]; i; i = edge2[i].nextt) { int to = edge2[i].to; dis[to] = max(dis[to], dis[now] + val[to]); if(!--in[to]) q.push(to); } } for(int i = 1; i <= n; i++) ans = max(ans, dis[i]); } int main() { cin >> n >> m; for(int i = 1; i <= n; i++) cin >> val[i]; for(int i = 1; i <= m; i++) { cin >> u >> v; addedge1(u, v); } for(int i = 1; i <= n; i++) { if(!dfn[i]) tarjan(i); } for(int i = 1; i <= m; i++) { int u = sd[edge1[i].u], v = sd[edge1[i].to]; if(u == v) continue; addedge2(u, v); in[v] ++; } topo(); cout << ans << endl; return 0; }
Luogu 3627 抢掠计划
先缩点,后跑一遍$spfa$(最长路)即可。
#include <bits/stdc++.h> const int MAXN = 500050; const int INF = 1 << 30; using namespace std; struct Edge1 { int u, to, nextt; }edge1[MAXN]; struct Edge2 { int u, to, val, nextt; }edge2[MAXN]; stack<int> d; queue<int> q; int n, m, u, v, w, x, num1, num2, cnt, cnt1, s, p, ans, sd[MAXN], head1[MAXN], head2[MAXN], low[MAXN], dfn[MAXN], vall[MAXN], dis[MAXN], sccno[MAXN]; bool vis[MAXN]; int read() { int x = 0; bool sign = false; char alpha = 0; while(!isdigit(alpha)) { sign |= alpha == '-'; alpha = getchar(); } while(isdigit(alpha)) { x = (x << 1) + (x << 3) + (alpha ^ 48); alpha = getchar(); } return sign ? -x : x; } void addedge1(int u, int v) { edge1[++num1].u = u; edge1[num1].to = v; edge1[num1].nextt = head1[u]; head1[u] = num1; } void addedge2(int u, int v, int w) { edge2[++num2].u = u; edge2[num2].to = v; edge2[num2].val = w; edge2[num2].nextt = head2[u]; head2[u] = num2; } void tarjan(int x) { dfn[x] = low[x] = ++cnt; d.push(x); for(int i = head1[x]; i; i = edge1[i].nextt) { int to = edge1[i].to; if(!dfn[to]) { tarjan(to); low[x] = min(low[x], low[to]); } else if(!sccno[to]) low[x] = min(low[x], dfn[to]); } if(dfn[x] == low[x]) { ++cnt1; while(true) { int now = d.top(); d.pop(); sccno[now] = cnt1; sd[now] = x; if(x == now) break; vall[x] += vall[now]; } } } void spfa() { dis[sd[s]] = vall[sd[s]]; q.push(sd[s]); while(!q.empty()) { int now = q.front(); q.pop(); vis[now] = false; for(int i = head2[now]; i; i = edge2[i].nextt) { int to = edge2[i].to, val = edge2[i].val; if(dis[to] < dis[now] + val) { dis[to] = dis[now] + val; if(!vis[to]) { vis[to] = true; q.push(to); } } } } } int main() { n = read(); m = read(); for(int i = 1; i <= m; i++) { u = read(); v = read(); addedge1(u, v); } for(int i = 1; i <= n; i++) vall[i] = read(); for(int i = 1; i <= n; i++) { if(!dfn[i]) tarjan(i); } for(int i = 1; i <= m; i++) { u = sd[edge1[i].u]; v = sd[edge1[i].to]; w = vall[v]; if(u == v) continue; addedge2(u, v, w); } s = read(); p = read(); spfa(); for(int i = 1; i <= p; i++) { x = read(); ans = max(ans, dis[sd[x]]); } cout << ans << endl; return 0; }
Luogu 2002 消息扩散
答案显然是缩点后入度为$0$的点的个数。
#include <bits/stdc++.h> const int MAXN = 100050; const int MAXM = 500500; using namespace std; stack<int> ss; int n, m, u, v, pos, num, num1, num2, cnt, cnt1, in[MAXN], head1[MAXN], head2[MAXN], low[MAXN], dfn[MAXN], sd[MAXN], sccno[MAXN]; bool vis[5050][5050]; struct Edge1 { int u, to, nextt; }edge1[MAXM]; struct Edge2 { int u, to, nextt; }edge2[MAXM]; int read() { int x = 0; bool sign = false; char alpha = 0; while(!isdigit(alpha)) { sign |= alpha == '-'; alpha = getchar(); } while(isdigit(alpha)) { x = (x << 1) + (x << 3) + (alpha ^ 48); alpha = getchar(); } return sign ? -x : x; } void addedge1(int u, int v) { edge1[++num1].u = u; edge1[num1].to = v; edge1[num1].nextt = head1[u]; head1[u] = num1; } void addedge2(int u, int v) { edge2[++num2].u = u; edge2[num2].to = v; edge2[num2].nextt = head2[u]; head2[u] = num2; } void tarjan(int x) { dfn[x] = low[x] = ++cnt; ss.push(x); for(int i = head1[x]; i; i = edge1[i].nextt) { int to = edge1[i].to; if(!dfn[to]) { tarjan(to); low[x] = min(low[x], low[to]); } else if(!sccno[to]) low[x] = min(low[x], dfn[to]); } if(dfn[x] == low[x]) { ++cnt1; while(true) { int now = ss.top(); ss.pop(); sccno[now] = cnt1; sd[now] = cnt1; if(now == x) break; } } } int main() { n = read(); m = read(); for(int i = 1; i <= m; i++) { u = read(); v = read(); if(u == v) continue; addedge1(u, v); } for(int i = 1; i <= n; i++) { if(!dfn[i]) tarjan(i); } for(int i = 1; i <= num1; i++) { u = sd[edge1[i].u]; v = sd[edge1[i].to]; if(u == v) continue; in[v]++; addedge2(u, v); } for(int i = 1; i <= cnt1; i++) { if(!in[i]) num++; } cout << num << endl; return 0; }
Luogu 2746 Network of Schools
第一问的答案显然是缩点后入度为$0$的点的个数。
对于第二问,若存在出度为$0$的点或者入度为$0$的点则不满足条件。若把一个出度为$0$的点与一个入度为$0$的点连一条边,问题便迎刃而解。
考虑到两种点的个数不一定相同,选取其中的最大值即可。
记得判断是一个大环的情况。
#include <bits/stdc++.h> const int MAXN = 250000; using namespace std; struct Edge1 { int u, to, nextt; }edge1[MAXN << 1]; struct Edge2 { int u, to, nextt; }edge2[MAXN << 1]; stack<int> s; int n, u ,v, cnt, cnt1, ans1, num, num1, num2, head1[MAXN], head2[MAXN], dfn[MAXN], low[MAXN], sccno[MAXN], sd[MAXN], in[MAXN], out[MAXN]; void addedge1(int u, int v) { edge1[++num1].u = u; edge1[num1].to = v; edge1[num1].nextt = head1[u]; head1[u] = num1; } void addedge2(int u, int v) { edge2[++num2].u = u; edge2[num2].to = v; edge2[num2].nextt = head2[u]; head2[u] = num2; } void tarjan(int x) { dfn[x] = low[x] = ++cnt; s.push(x); for(int i = head1[x]; i; i = edge1[i].nextt) { int to = edge1[i].to; if(!dfn[to]) { tarjan(to); low[x] = min(low[x], low[to]); } else if(!sccno[to]) low[x] = min(low[x], dfn[to]); } if(low[x] == dfn[x]) { ++cnt1; while(true) { int now = s.top(); s.pop(); sd[now] = cnt1; sccno[now] = cnt1; if(now == x) break; } } } int main() { cin >> n; for(int i = 1; i <= n; i++) { while(scanf("%d", &v) == 1) { if(v == 0) break; addedge1(i, v); } } for(int i = 1; i <= n; i++) { if(!dfn[i]) tarjan(i); } for(int i = 1; i <= num1; i++) { u = sd[edge1[i].u]; v = sd[edge1[i].to]; if(u == v) continue; addedge2(u, v); in[v]++; out[u]++; } for(int i = 1; i <= cnt1; i++) { if(in[i] == 0) ans1++; if(out[i] == 0) num++; } cout << ans1 << endl; if(cnt1 == 1) { cout << "0" << endl; return 0; } cout << max(num, ans1) << endl; return 0; }
Luogu 2341 受欢迎的牛
若出现个数 ge 1的出度为$0$的强连通分量,则无解。
$otherwise$答案即为出度为$0$的强连通分量的大小。
#include <bits/stdc++.h> const int MAXN = 10050; const int MAXM = 50050; using namespace std; stack<int> ss; int n, m, u, v, pos, num, num1, num2, cnt, cnt1, head1[MAXN], head2[MAXN], low[MAXN], dfn[MAXN], sd[MAXN], f[MAXN], sccno[MAXN]; struct Edge1 { int u, to, nextt; }edge1[MAXM]; struct Edge2 { int u, to, nextt; }edge2[MAXM]; int read() { int x = 0; bool sign = false; char alpha = 0; while(!isdigit(alpha)) { sign |= alpha == '-'; alpha = getchar(); } while(isdigit(alpha)) { x = (x << 1) + (x << 3) + (alpha ^ 48); alpha = getchar(); } return sign ? -x : x; } void addedge1(int u, int v) { edge1[++num1].u = u; edge1[num1].to = v; edge1[num1].nextt = head1[u]; head1[u] = num1; } void addedge2(int u, int v) { edge2[++num2].u = u; edge2[num2].to = v; edge2[num2].nextt = head2[u]; head2[u] = num2; } void tarjan(int x) { dfn[x] = low[x] = ++cnt; ss.push(x); for(int i = head1[x]; i; i = edge1[i].nextt) { int to = edge1[i].to; if(!dfn[to]) { tarjan(to); low[x] = min(low[x], low[to]); } else if(!sccno[to]) low[x] = min(low[x], dfn[to]); } if(dfn[x] == low[x]) { ++cnt1; while(true) { int now = ss.top(); ss.pop(); sccno[now] = cnt1; sd[now] = cnt1; f[cnt1] ++; if(now == x) break; } } } int main() { n = read(); m = read(); for(int i = 1; i <= m; i++) { u = read(); v = read(); addedge1(u, v); } for(int i = 1; i <= n; i++) { if(!dfn[i]) tarjan(i); } for(int i = 1; i <= m; i++) { u = sd[edge1[i].u]; v = sd[edge1[i].to]; if(u == v) continue; addedge2(u, v); } for(int i = 1; i <= cnt1; i++) { if(!head2[i]) { pos = i; num++; } if(num == 2) { cout << "0" << endl; return 0; } } cout << f[pos] << endl; return 0; }
Luogu 1262 间谍网络
考虑无解的情况。若存在一个点既不能被贿赂,也没有入度,则不存在答案。
先缩点。对于现在没有入度且原先在强连通分量中的点,代价即为这些点中花费最小的值,累加即可。
#include <bits/stdc++.h> const int MAXN = 100050; const int INF = 1 << 30; int n, m, u, v, p, num1, num2, cnt, cnt1, ans, in[MAXN], dfn[MAXN], low[MAXN], head1[MAXN], head2[MAXN], sccno[MAXN], val[MAXN], vall[MAXN], sd[MAXN]; using namespace std; stack<int> s; struct Edge1 { int u, to, nextt; }edge1[MAXN]; int read() { int x = 0; bool sign = false; char alpha = 0; while(!isdigit(alpha)) { sign |= alpha == '-'; alpha = getchar(); } while(isdigit(alpha)) { x = (x << 1) + (x << 3) + (alpha ^ 48); alpha = getchar(); } return sign ? -x : x; } void addedge1(int u, int v) { edge1[++num1].u = u; edge1[num1].to = v; edge1[num1].nextt = head1[u]; head1[u] = num1; } void tarjan(int x) { dfn[x] = low[x] = ++cnt; s.push(x); for(int i = head1[x]; i; i = edge1[i].nextt) { int to = edge1[i].to; if(!dfn[to]) { tarjan(to); low[x] = min(low[x], low[to]); } else if(!sccno[to]) low[x] = min(low[x], dfn[to]); } if(dfn[x] == low[x]) { ++cnt1; while(true) { int now = s.top(); s.pop(); sd[now] = cnt1; sccno[now] = cnt1; vall[cnt1] = min(vall[cnt1], val[now]); if(now == x) break; } } } int main() { n = read(); p = read(); for(int i = 1; i <= n; i++) val[i] = vall[i] = INF; for(int i = 1; i <= p; i++) { u = read(); val[u] = read(); } m = read(); for(int i = 1; i <= m; i++) { u = read(); v = read(); in[v]++; addedge1(u, v); } for(int i = 1; i <= n; i++) { if(val[i] == INF && in[i] == 0) { cout << "NO" << endl << i << endl; return 0; } } for(int i = 1; i <= n; i++) { if(!dfn[i] && val[i] != INF) tarjan(i); } for(int i = 1; i <= n; i++) in[i] = 0; for(int i = 1; i <= num1; i++) { u = sd[edge1[i].u]; v = sd[edge1[i].to]; if(u == v) continue; in[v]++; } for(int i = 1; i <= cnt1; i++) { if(in[i] == 0) ans += vall[i]; } cout << "YES" << endl << ans << endl; return 0; }