Tarjan算法
Tarjan算法
处理强连通分量
例题(banziti)缩点
为了让所有强连通分量都缩成一个点,我们运用一个dfs来解决问题。
首先,有一个数组dfn[]记录的是遍历的顺序。还有一个数组low[]记录的是该点能到达的最小dfn的点。每一次遍历就更新一次dfn[]和low[],将元素push入栈。当更新完后,low[x]=dfn[x]时,就说明这里出现了一个强连通分量,就把栈中元素pop出来,一直pop到元素为x为止,同时更新节点x的点权,更新这些点缩成的点为x。
缩完点后,我们应建一张新的图来存储现在这个有向无环图,为了遍历每一个新的节点,我们采用拓扑排序来进行遍历,同时维护数组maxn,转移方程为maxn[y]=max(maxn[y],maxn[x]+val[y])。
最后遍历一遍所有的maxn[x],找出最大值,这就是本题的答案。
代码实现如下:
1 #include<bits/stdc++.h> 2 using namespace std; 3 4 const int N=200009; 5 int tot,cnt,idx,ans,n,m,tott; 6 bool vis[N],inst[N]; 7 int st[N],val[N],in[N],scc[N],low[N],dfn[N]; 8 int to[N],nex[N],from[N],head[N],maxn[N]; 9 int too[N],nexx[N],fromm[N],headd[N]; 10 11 void add(int u,int v){ 12 to[++tot]=v;from[tot]=u; 13 nex[tot]=head[u];head[u]=tot; 14 } 15 16 void top_sort(){//拓扑排序 17 queue<int> q; 18 for(int i=1;i<=n;i++){ 19 if(scc[i]==i&&!in[i]){//不是强连通分量的点且入度为0 20 q.push(i); 21 maxn[i]=val[i]; 22 } 23 } 24 while(!q.empty()){ 25 int x=q.front();q.pop(); 26 for(int i=headd[x];i;i=nexx[i]){//注意,这是新图 27 int y=too[i]; 28 maxn[y]=max(maxn[y],maxn[x]+val[y]);//看是否能更新 29 in[y]--;//点y入度减一 30 if(!in[y]) q.push(y); 31 } 32 } 33 } 34 35 void dfs(int x){ 36 st[++cnt]=x;//进栈 37 low[x]=dfn[x]=++idx; 38 vis[x]=true;inst[x]=true;//vis装的是访问过没有,inst装的是是否在栈中 39 for(int i=head[x];i;i=nex[i]){ 40 int y=to[i]; 41 if(!vis[y]){ 42 dfs(y); 43 low[x]=min(low[x],low[y]); 44 } 45 else if(inst[y]){ 46 low[x]=min(low[x],dfn[y]); 47 } 48 } 49 if(low[x]==dfn[x]){//有强连通分量 50 int k=0; 51 int ttt=0; 52 while(k!=x){ 53 k=st[cnt--];//出栈 54 inst[k]=false; 55 scc[k]=x;//scc装的是它们被缩成了哪一个点,一开始值都为它们自己 56 ttt+=val[k]; 57 } 58 val[x]=ttt;//更新val为整个强连通分量的点权之和 59 } 60 } 61 62 signed main(){ 63 cin>>n>>m; 64 tot=1; 65 for(int i=1;i<=n;i++){cin>>val[i];}//点权 66 for(int i=1;i<=m;i++){ 67 int u,v; 68 cin>>u>>v; 69 add(u,v);//加边 70 } 71 for(int i=1;i<=n;i++) if(!dfn[i]) dfs(i);//dfn为0说明该点未被访问过 72 for(int i=1;i<=m;i++){ 73 int y=to[i],x=from[i]; 74 if(scc[x]!=scc[y]){//有路径相连的两个点若不在一个强连通分量中,就建一条路将它们相连 75 too[++tott]=scc[y];fromm[tott]=scc[x];//注意,这是新图 76 nexx[tott]=headd[scc[x]];headd[scc[x]]=tott; 77 in[scc[y]]++;//处理入度 78 } 79 } 80 top_sort();//拓扑排序 81 for(int i=1;i<=n;i++) ans=max(ans,maxn[i]);//最大点权 82 cout<<ans; 83 return 0; 84 }
割点&割边(桥)
给定无向连通图G=(V,E):
若对于x∈V,从图中删去节点x以及所有与x关联的边之后,G分裂成两个或两个以上不相连的子图,则称x是G的割点。
若对于e∈E,从图中删去边e之后,G分裂成两个不相连的子图,则称e是G的割边或桥。
割点就是要满足该点儿子的low值≤该点的dfn序。即是说该点的儿子不可以通过其他一些道路回到该点以前的点。注意,当判断的是搜索树的根节点时,要有两条边满足该条件。
割边就是要满足该边端点的low值<该边起点的dfn序。无其余特殊要求。
由于割点和割边的实现都与Tarjan差不多,就直接上代码。
割点核心代码
1 void dfs(int x){ 2 low[x]=dfn[x]=++idx; 3 int flag=0; 4 for(int i=head[x];i;i=nex[i]){ 5 int y=to[i]; 6 if(!dfn[y]){ 7 dfs(y); 8 low[x]=min(low[x],low[y]); 9 if(low[y]>=dfn[x]){ 10 flag++; 11 if(flag>1||x!=root) isc[x]=true; 12 } 13 } 14 else{low[x]=min(low[x],dfn[y]);} 15 } 16 }
割边核心代码
1 void dfs(int x,int in){ 2 dfn[x]=low[x]=++idx; 3 for(int i=head[x];i;i=nex[i]){ 4 int y=to[i]; 5 if(!dfn[y]){ 6 dfs(y,i); 7 low[x]=min(low[x],low[y]); 8 if(low[y]>dfn[x]){ 9 isc[i]=isc[i^1]=true; 10 } 11 } 12 else if(i!=in^1)low[x]=min(low[x],dfn[y]); 13 } 14 }
THE END...