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...

 

posted @ 2021-02-04 09:55  001A  阅读(30)  评论(0编辑  收藏  举报