tarjan用来求有向图上强连通分量
强连通分量就是图的一个子图,这个子图上任意两点都可以相互到达
tarjan是基于dfs的算法,从一个点开始探索,dfn数组存每点探索的时间戳,low数组存每点下面能找到的最小的时间戳
显然当一个点dfn==low时 这个点是一个强连通分量的根
虽然是dfs不过没有回溯,每个点每个边只访问一次 复杂度O(n+m)
https://nanti.jisuanke.com/t/16955
前几天乌鲁木齐网络赛
给一个有向图问最少添加几条边可以让图强连通
其实每个强连通分量可以看成一个点(反正这里面每两个点可达)
要整个图连通, 每个“点”都要有入度和出度,这时只需要遍历每条边
如果边u->v连接了两个不同的分量,那就out[flag[u]]++,in[flag[v]]++,表示这两个联通分量一个有出度一个有入度
u->v连接相同分量不用管
最后对每个连通分量统计一下缺的出度入度,至少要补完这些出度入度,所以输出大的值
#include<cstdio> #include<stack> #include<cstring> #include<algorithm> using namespace std; const int maxn = 1e4+7, maxm = 1e5+7; struct edge{ int v, nxt; edge(){} edge(int v, int nxt):v(v), nxt(nxt){} }e[maxm]; int head[maxn], ins[maxn], dfn[maxn], low[maxn], flag[maxn], cur, Index, cnt; int in[maxn], out[maxn], n, m; stack<int>S; void addedge(int u, int v){ e[cur] = edge(v, head[u]); head[u] = cur++; } void tarjan(int u){ dfn[u] = low[u] = Index++; ins[u] = 1; S.push(u); for(int i = head[u]; ~i; i = e[i].nxt){ int v = e[i].v; if(dfn[v] == -1){ tarjan(v); low[u] = min(low[u], low[v]); } else if(ins[v]){ low[u] = min(low[u], dfn[v]); } } if(low[u] == dfn[u]){ cnt++; while(1){ int tmp = S.top(); S.pop(); ins[tmp] = 0; flag[tmp] = cnt; if(tmp == u) break; } } } void init(){ memset(head, -1, sizeof(head)); memset(ins, 0, sizeof(ins)); memset(dfn, -1, sizeof(dfn)); memset(low, -1, sizeof(low)); memset(flag, 0, sizeof(flag)); memset(in, 0, sizeof(in)); memset(out, 0, sizeof(out)); cur = Index = cnt = 0; } void work(){ for(int i = 1; i <= n; i++){ if(dfn[i] == -1) tarjan(i); } for(int i = 1; i <= n; i++){ for(int j = head[i]; ~j; j = e[j].nxt){ int v = e[j].v; if(flag[i] != flag[v]){ out[flag[i]]++; in[flag[v]]++; } } } int lossin = 0, lossout = 0; for(int i = 1; i <= cnt; i++){ if(!in[i]) lossin++; if(!out[i]) lossout++; } if(cnt == 1){ puts("0"); } else{ printf("%d ", max(lossin, lossout)); } } int main(){ int t; scanf("%d", &t); while(t--){ init(); scanf("%d%d", &n, &m); while(m--){ int u, v; scanf("%d%d", &u, &v); addedge(u, v); } work(); } return 0; }