Tarjan-有向图
(我到底是咕了多少知识点啊)
在有向图中tarjan主要用来求强连通分量并缩点
一、定义
强连通:如果两个顶点可以相互通达,则称两个顶点 强连通
强连通分量:如果有向图G的每两个顶点都 强连通,称G是一个强连通图。非 强连通图有向图的极大强连通子图,称为强连通分量
树枝边:原图上的边中,在DFS树上由父亲指向儿子的叫树枝边
前向边:由父亲指向儿子以下的其他后代的叫前向边
后向边:由后代指向祖先的边叫后向边
横叉边:一个子树中的点指向另一个子树中的点的边叫横叉边
二、tarjan
用来求强联通分量
基于dfs
每个强连通分量为搜索树中的一颗子树
三、算法
首先要引入两个非常重要的数组:dfn[ ] 和 low[ ]
dfn[ ] :就是一个时间戳(被dfs到的次序)。
low [ ] : 该子树中,且仍在栈中的最小时间戳(即可以到达的图中dfn值最小的点得dfn值)
这个图不一定是一个连通图,所以跑tarjan的时候要枚举每个点
若dfn[ ] == 0,进行深搜
然后对于搜到的点寻找与其有边相连的点,判断这些点是否已经被搜索过,若没有,则进行搜索。若该点已经入栈,说明形成了环,则更新low.
在不断深搜的过程中如果没有路可走了(出边遍历完了),那么就进行回溯,回溯时不断比较low[ ],去最小的low值。如果dfn[x]==low[x]则x可以看作是某一强连通分量子树的根,也说明找到了一个强连通分量,然后对栈进行弹出操作,直到x被弹出。
洛谷缩点板子题
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; inline int read() { int sum = 0,p = 1; char ch = getchar(); while(ch < '0' || ch > '9') { if(ch == '-') p = -1; ch = getchar(); } while(ch >= '0' && ch <= '9') { (sum *= 10) += ch - '0'; ch = getchar(); } return sum * p; } const int maxn = 1e5+1e4,maxm = 1e5 + 1e4; int vis[maxn],dfn[maxn],low[maxn],tim; int ncnt,bel[maxn],sum[maxn],f[maxn]; int cnt,head[maxn],nxt[maxm],to[maxm]; int sta[maxn],top; int n,m,val[maxn],x[maxm],y[maxm],ans; void add(int a,int b) { nxt[++cnt] = head[a]; to[cnt] = b; head[a] = cnt; } void tarjan(int x) { dfn[x] = low[x] = ++tim; sta[++top] = x; vis[x] = 1; 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(vis[v]) low[x] = min(low[x],dfn[v]); } if(low[x] == dfn[x]) { ncnt++; while(sta[top+1] != x) { bel[sta[top]] = ncnt; sum[ncnt] += val[sta[top]]; vis[sta[top--]] = 0; } } } void search(int x) { if(f[x]) return; f[x] = sum[x]; int maxsum = 0; for(int i = head[x];i;i = nxt[i]) { if(!f[to[i]]) search(to[i]); maxsum = max(maxsum,f[to[i]]); } f[x] += maxsum; } int main() { n = read(); m = read(); for(int i = 1;i <= n;i++) val[i] = read(); for(int i = 1;i <= m;i++) { x[i] = read(); y[i] = read(); add(x[i],y[i]); } for(int i = 1;i <= n;i++) if(!dfn[i]) tarjan(i); memset(head,0,sizeof(head)); memset(nxt,0,sizeof(nxt)); memset(to,0,sizeof(to)); cnt = 0; for(int i = 1;i <= m;i++) { if(bel[x[i]] != bel[y[i]]) add(bel[x[i]],bel[y[i]]); } for(int i = 1;i <= ncnt;i++) if(!f[i]) { search(i); ans = max(ans,f[i]); } printf("%d",ans); return 0; }