Kosoraju
SCC总数及记录SCC所需要的最少边情况
#include<cstdio> const int N = 300010; int scc_cnt = 0; int Case, n, m, i, x, y, cnt, use[N], g[2][N], nxt[2][N], v[2][N], ed, q[N], t, vis[N], e[N][2]; inline void add(int x, int y) { v[0][++ed] = y; nxt[0][ed] = g[0][x]; g[0][x] = ed; v[1][ed] = x; nxt[1][ed] = g[1][y]; g[1][y] = ed; } void dfs1(int x) { vis[x] = 1; for (int i = g[0][x]; i; i = nxt[0][i]) if (!vis[v[0][i]]) { use[i] = 1, dfs1(v[0][i]); } q[++t] = x; } void dfs2(int x) { vis[x] = 0; for (int i = g[1][x]; i; i = nxt[1][i]) if (vis[v[1][i]]) { use[i] = 1, dfs2(v[1][i]); } } int main() { scanf("%d", &Case); while (Case--) { scc_cnt = 0; scanf("%d%d", &n, &m); for (ed = 0, i = 1; i <= n; i++) { vis[i] = g[0][i] = g[1][i] = 0; } for (i = 1; i <= m; i++) { scanf("%d%d", &x, &y); e[i][0] = x, e[i][1] = y; add(x, y); use[i] = 0; } for (t = 0, i = 1; i <= n; i++) if (!vis[i]) { dfs1(i); } for (i = n; i; i--) if (vis[q[i]]) { scc_cnt++; dfs2(q[i]); } printf("%d ",scc_cnt); // cnt = n * 2; // for (i = 1; i <= m; i++)if (use[i]) // { // cnt--; // } // for (i = 1; i <= m; i++)if (!use[i] && cnt > 0) // { // use[i] = 1, cnt--; // } // for (i = 1; i <= m; i++)if (!use[i]) // { // printf("%d %d ", e[i][0], e[i][1]); // } } }
Tarjan
转自:
https://www.cnblogs.com/stxy-ferryman/p/7779347.html
https://blog.csdn.net/sentimental_dog/article/details/53790582
用处:
1.有向图强连通分量 (一个有向图是强连通的当且仅当G中有一个回路,它至少包含每个节点一次)
2.无向图双连通分量(割点,桥)
3.离线LCA
割顶:若去掉一个点和与这个点相连的边后,图不再连通,则这个点是割顶。
求法:若节点uu存在一棵子树vv满足vv中所有节点的回边都指向uu及以下的节点(即low[v]≥pre[u]low[v]≥pre[u]),则uu是割顶;所以一次DFS即可求出所有割顶。但注意一个特殊情况!根节点是割顶当且仅当它的子节点数严格大于1。
桥:去掉这条边,图不再连通。
求法:若节点uu存在一棵子树vv满足vv中所有节点的回边都指向uu以下(不含uu)的节点(即low[v]>pre[u]low[v]>pre[u]),则边(u,v)(u,v)是桥;所以一次DFS即可求出所有桥。
BCC(点-双连通分量):连通分量中任意两点间都至少有两条“点不重复路径”。
求法:点双等价于不含割顶的极大子图。但一个问题是割顶本身可能属于多个点双。解决方法是把边分开,即保存一个边的栈。
eBCC(边-双连通分量):连通分量中任意两点间都至少有两条“边不重复路径”。
求法:边双等价于不含桥的极大子图。一条点不可能分属多个边双(否则可以将这些个边双合并)。所以一次DFS求出所有桥,再来一次DFS把边双分开即可(DFS到桥就不走)。
前向星版SCC
void tarjan(int x) { dfn[x] = ++deep; low[x] = deep; visit[x] = 1; sta[++top] = x; for (int i = Head[x]; i; i = nxt[i]) { int v = to[i]; if (!dfn[v]) { tarjan(v); low[x] = min(low[x], low[v]); } else { if (visit[v]) { low[x] = min(low[x], low[v]); } } } if (dfn[x] == low[x]) { color[x] = ++colorsum; visit[x] = 0; while (sta[top] != x) { color[sta[top]] = colorsum; visit[sta[top--]] = 0; } top--; } }
[USACO06JAN]The Cow Prom
算强连通分量中点数不小于1的个数
/*#include<cstring>#include<algorithm>#include<queue>#include<vector>#include<cstdio>#include<cmath>#include<iostream>*/ #include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 100005; vector<int> gra[maxn]; int dfn[maxn];//表示这个点在dfs的时候是第几个搜到的; int low[maxn];//表示这个点及其子节点连的所有点里dfn的最小值 int sta[maxn];//存着当前所有可能能构成强连通分量的点 int visit[maxn];//表示一个点目前是否在sta中 int cnt[maxn];//各个强连通分量中含点的数目 int color[maxn];//表示一个点属于哪个强连通分量 int deep;/*表示从前有多少个点被搜到*/ int top;/*sta目前的大小*/ int colorsum = 0;/*目前强连通分量的数目*/ int ans = 0; int n, m; void tarjan(int x) { dfn[x] = ++deep; low[x] = deep; visit[x] = 1; sta[++top] = x; int sz = gra[x].size(); for (int i = 0; i < sz; i++) { int to = gra[x][i]; if (!dfn[to]) { tarjan(to); low[x] = min(low[x], low[to]); } else { if (visit[to]) { low[x] = min(low[x], low[to]); } } } if (dfn[x] == low[x]) { color[x] = ++colorsum; visit[x] = 0; while (sta[top] != x) { color[sta[top]] = colorsum; visit[sta[top--]] = 0; } top--; } } int main() { cin >> n >> m; int from, to; for (int i = 1; i <= m; i++) { cin >> from >> to; gra[from].push_back(to); } for (int i = 1; i <= n; i++) { if (!dfn[i]) { tarjan(i); } } for (int i = 1; i <= n; i++) { cnt[color[i]]++; } for (int i = 1; i <= colorsum; i++) { if (cnt[i] > 1) { ans++; } } cout << ans << endl; }
poj2186 Popular Cows
缩点后求出度为0的点的个数(如果求得的点为缩点后的超级点需要特判)
/*#include<cstring>#include<algorithm>#include<queue>#include<vector>#include<cstdio>#include<cmath>#include<iostream>*/ #include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 100005; vector<int> gra[maxn]; int du[maxn]; int dfn[maxn];//表示这个点在dfs的时候是第几个搜到的; int low[maxn];//表示这个点及其子节点连的所有点里dfn的最小值 int sta[maxn];//存着当前所有可能能构成强连通分量的点 int visit[maxn];//表示一个点目前是否在sta中 int cnt[maxn];//各个强连通分量中含点的数目 int color[maxn];//表示一个点属于哪个强连通分量 int deep;/*表示从前有多少个点被搜到*/ int top;/*sta目前的大小*/ int colorsum = 0;/*目前强连通分量的数目*/ int ans = 0; int n, m; void tarjan(int x) { dfn[x] = ++deep; low[x] = deep; visit[x] = 1; sta[++top] = x; int sz = gra[x].size(); for (int i = 0; i < sz; i++) { int to = gra[x][i]; if (!dfn[to]) { tarjan(to); low[x] = min(low[x], low[to]); } else { if (visit[to]) { low[x] = min(low[x], low[to]); } } } if (dfn[x] == low[x]) { color[x] = ++colorsum; visit[x] = 0; while (sta[top] != x) { color[sta[top]] = colorsum; visit[sta[top--]] = 0; } top--; } } int main() { int temp = 0; cin >> n >> m; int from, to; for (int i = 1; i <= m; i++) { cin >> from >> to; gra[from].push_back(to); } for (int i = 1; i <= n; i++) { if (!dfn[i]) { tarjan(i); } } for (int i = 1; i <= n; i++) { int len = gra[i].size(); for (int j = 0; j < len; j++) { int to = gra[i][j]; if (color[to] != color[i]) //如果缩点后两个点不在一个强连通分量内 { du[color[i]]++; } } cnt[color[i]]++; } for (int i = 1; i <= colorsum; i++) { if (temp > 1) { cout << 0 << endl; return 0; } if (du[i] == 0) { temp++; ans = cnt[i]; } } cout << ans << endl; }
洛谷3388 求割点并输出模板题(附带桥输出)
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 100005; vector<int> gra[maxn]; int dfn[maxn];//表示这个点在dfs的时候是第几个搜到的; int low[maxn];//表示这个点及其子节点连的所有点里dfn的最小值 int iscut[maxn];//表示这个点是否是割点 int deep;/*表示从前有多少个点被搜到*/ int ans = 0; int n, m; int tarjan(int x, int pre) { int child = 0; int lowx; lowx = dfn[x] = ++deep; int len = gra[x].size(); for (int i = 0; i < len; i++) { int to = gra[x][i]; if (!dfn[to]) { child++; int lowto = tarjan(to, x); lowx = min(lowx, lowto); if (lowto >= dfn[x]) { iscut[x] = 1; } // if (lowto > dfn[x]) // { // cout << "bridge " << x << " " << to << endl; // } } else { if (to != pre && dfn[to] < dfn[x]) { lowx = min(lowx, dfn[to]); } } } if (pre < 0 && child == 1) { iscut[x] = 0; } low[x] = lowx; return lowx; } int main() { int temp = 0; cin >> n >> m; int from, to; for (int i = 1; i <= m; i++) { cin >> from >> to; gra[from].push_back(to); gra[to].push_back(from); } for (int i = 1; i <= n; i++) { if (!dfn[i]) { tarjan(i, -1); } } for (int i = 1; i <= n; i++) { if (iscut[i]) { ans++; } } cout << ans << endl; for (int i = 1; i <= n; i++) { if (iscut[i]) { cout << i << " "; } } cout << endl; }
Codeforces 962 F
求点双连通分量中的边
/*Huyyt*/ #include<bits/stdc++.h> #define mem(a,b) memset(a,b,sizeof(a)) #define pb push_back using namespace std; typedef long long ll; typedef unsigned long long ull; const int dir[8][2] = {{0, 1}, {1, 0}, {0, -1}, { -1, 0}, {1, 1}, {1, -1}, { -1, -1}, { -1, 1}}; const int mod = 1e9 + 7; const int gakki = 5 + 2 + 1 + 19880611 + 1e9; const int MAXN = 2e5 + 5; const int MAXM = 2e5 + 5; int to[MAXM << 1], nxt[MAXM << 1], Head[MAXN], tot = 1; inline void addedge(int u, int v) { to[++tot] = v; nxt[tot] = Head[u]; Head[u] = tot; } int n, m; int dfn[MAXN], low[MAXN], dfs_clock = 0; int BCCcnt = 0, blong[MAXN], inque[MAXM << 1]; int st[MAXN], l = 0, ans[MAXN], ansnum = 0; bool vis[MAXM << 1]; void tarjanBCC(int x, int fa) { dfn[x] = low[x] = ++dfs_clock; for (int i = Head[x]; i; i = nxt[i]) { int v = to[i]; if (v == fa || vis[i]) { continue; } vis[i] = vis[i ^ 1] = 1; st[l++] = i; if (!dfn[v]) { tarjanBCC(v, x); low[x] = min(low[v], low[x]); if (dfn[x] <= low[v]) { int now, vnumber = 0, enumber = 0; BCCcnt++; while(1) { now = st[--l]; if (blong[to[now]] != BCCcnt) { blong[to[now]] = BCCcnt, ++vnumber; } if (blong[to[now ^ 1]] != BCCcnt) { blong[to[now ^ 1]] = BCCcnt, ++vnumber; } inque[++enumber] = now; if(now==i) break; } if (vnumber == enumber) { for (int i = 1; i <= enumber; i++) { ans[++ansnum] = inque[i] / 2; } } } } else { low[x] = min(low[x], dfn[v]); } } } int main() { ios_base::sync_with_stdio(0); cin.tie(0); cin >> n >> m; int u, v; 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]) { tarjanBCC(i, -1); } } sort(ans + 1, ans + 1 + ansnum); cout << ansnum << endl; for (int i = 1; i <= ansnum; i++) { cout << ans[i] << " "; } return 0; }
附:求边双连通分量中的边
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int N=1e5+88; int dfn[N],low[N],H[N],nxt[N<<1],to[N<<1]; int n,m,x,y,cnt,tot=1; bool ib[N],is[N]; void add(int x,int y){ to[++tot]=y;nxt[tot]=H[x];H[x]=tot; } void dfs(int u,int fa){ low[u]=dfn[u]=++cnt; int chi=0; for(int i=H[u];i;i=nxt[i]){ int v=to[i]; if(v==fa) continue; if(!dfn[v]) { ++chi; dfs(v,u); low[u]=min(low[u],low[v]); if(low[v]>dfn[u]) ib[i]=ib[i^1]=1; if(low[v]>=dfn[u]) is[u]=1; } else low[u]=min(low[u],dfn[v]); } if(chi==1&&fa==-1) is[u]=0; } int num,bnum,a[N],A[N],ans; void dfs2(int u,int fa){ ++num;for(int i=H[u];i;i=nxt[i]) { int v=to[i]; if(ib[i]||ib[i^1]||v==fa) continue; ib[i]=ib[i^1]=1; a[++bnum]=i>>1; if(!dfn[v]) dfn[v]=1,dfs2(v,u); } } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=m;++i) { scanf("%d%d",&x,&y); add(x,y); add(y,x); } for(int i=1;i<=n;++i) if(!dfn[i]) dfs(i,-1); for(int i=0;i<=n;++i) dfn[i]=0; for(int i=1;i<=n;++i) if(!dfn[i]) { num=bnum=0; dfn[i]=1; dfs2(i,-1); if(num==bnum) for(int j=1;j<=num;++j) A[++ans]=a[j]; } printf("%d ",ans); sort(A+1,A+ans+1); for(int i=1;i<=ans;++i) printf("%d ",A[i]); }