图论 —— tarjan 缩点 割点 (学习历程)连载中......

首先,从模板题开始学起——        

    P3387 【模板】缩点

    思路:

      1. 这道题为什么要缩点?(什么时候需要缩点)

       根据题目意思,我们只需要找出一条点权最大的路径就行了,不限制点的个数。那么考虑对于一个环上的点被选择了,一整条环是不是应该都被选择,这一定很优,能选干嘛不选。很关键的是题目还允许我们重复经过某条边或者某个点,我们就不需要考虑其他了。因此整个环实际上可以看成一个点(选了其中一个点就应该选其他的点)

——摘录自洛谷第一篇题解

      2. 将强连通分量缩点 ->  建边成为DAG -> DPmax

  

 1 #include<cstdio>
 2 #include<queue>
 3 #include<vector>
 4 #include<cstring>
 5 #include<algorithm> 
 6 #define N 100010
 7 #define M 500010
 8 using namespace std;
 9 
10 struct node{
11     int from,to,next;
12 }edge[M];
13 queue < int > q;
14 vector < int > cd[N];        //出度 
15 vector  < int > rd[N];        //入度 
16 int ans[M],t,x,y,v,rds[N],u,n,m,sum,vis[N],d[N],dis[N];
17 int dfn[N],low[N],f[N],time,cnt,k;
18 int stack[N],head[M],visit[N],tot,id;
19 
20 void add(int x,int y){        //邻接表加边 
21     edge[++cnt].next=head[x];
22     edge[cnt].from=x;
23     edge[cnt].to=y;
24     head[x]=cnt;
25 }
26 
27 void tarjan(int x){
28     dfn[x]=low[x]=++time;        //更新时间
29     stack[++id]=x;        //手写栈 
30     visit[x]=1;        //入栈 
31     for(int i=head[x];i;i=edge[i].next){
32         if(!dfn[edge[i].to]){        //没有更新到的点 
33             tarjan(edge[i].to);
34             low[x]=min(low[x],low[edge[i].to]);
35         }
36         else{
37             if(visit[edge[i].to])        //更新过的点 
38                 low[x]=min(low[x],dfn[edge[i].to]);
39         }
40     }
41     if(low[x]==dfn[x]){        //注意不在 for 循环内 
42         tot++;        //强连通分量编号 (缩点的编号) 
43         while(1){
44             vis[stack[id]]=tot;        //vis记录缩点的编号 
45             dis[tot]+=d[stack[id]];        //缩点的权值=强连通分量权值累加
46             visit[stack[id]]=0,id--;        //出栈
47             if(x==stack[id+1]) break;        //这个连通块已弹完 
48         }
49     }
50 }
51 void topo(){        //拓扑排序 
52     for(int i=1;i<=tot;i++) if(rds[i]==0) q.push(i);        //入度数(rds)为 0 ,进队 
53     while(!q.empty()){
54         int u=q.front();
55         q.pop();        //队头出队 
56         ans[++k]=u;        //ans记录按拓扑序排列的点 
57         for(int i=1;i<=cd[u].size();i++){        //cd[u].size(): cd[u][]的长度 
58             v=cd[u][i-1];        //因为 vector是从 0开始的,所以减 1
59             rds[v]--;        //入度数-- 
60             if(rds[v]==0) q.push(v);        //入度数(rds)为 0 ,进队
61         }
62     }
63 }
64         
65 int main()
66 {
67     scanf("%d%d",&n,&m);
68     for(int i=1;i<=n;i++) scanf("%d",&d[i]);
69     for(int i=1;i<=m;i++) scanf("%d%d",&x,&y),add(x,y);
70         
71     for(int i=1;i<=n;i++)  if(!dfn[i]) tarjan(i);        // tarjan缩点 
72         
73     for(int i=1;i<=cnt;i++){        //把缩好的点 建边 ->DAG 
74         if(vis[edge[i].from]!=vis[edge[i].to]){
75             x=vis[edge[i].from],y=vis[edge[i].to];        // 建 x->y 的边 
76             rds[y]++;        // y 的入度数++ 
77             rd[y].push_back(x);        // 把 x 放入 y 的入度  
78             cd[x].push_back(y);        // 把 y 放入 x 的出度  
79         }
80     }
81     topo();        // DAG上跑拓扑 
82     for(int i=1;i<=tot;i++){        //按照拓扑序(无后效性) 跑 DP
83         int w=ans[i];
84         f[w]=dis[w];
85         for(int j=1;j<=rd[w].size();j++)
86             f[w]=max(f[w],f[rd[w][j-1]]+dis[w]);    //因为 vector是从 0开始的,所以减 1
87     }
88     
89     for(int i=1;i<=tot;i++) sum=max(f[i],sum);        //最后统计答案 
90     printf("%d",sum);
91     return 0;
92 }
View Code

     P3388 【模板】割点(割顶)

 1 #include<cstdio>
 2 #include<algorithm>
 3 #define N 100010
 4 using namespace std;
 5 struct node{
 6     int next,to;
 7 }edge[N*2];
 8 int n,m,dfn[N],cut[N],head[N],low[N],tot,cnt,id;
 9 
10 void add(int x,int y){
11     edge[++cnt].next=head[x];
12     edge[cnt].to=y;
13     head[x]=cnt;
14 }
15 
16 void  tarjan(int x,int root){        //割点 
17     int child=0;
18     dfn[x]=low[x]=++id;
19     for(int v,i=head[x];i;i=edge[i].next){
20         v=edge[i].to;
21         if(!dfn[v]){        //没有更新过的 
22             tarjan(v,root);
23             low[x]=min(low[x],low[v]);
24             if(low[v]>=dfn[x]&&x!=root) cut[x]=1;    //x不为根的割点判断条件: low[v]>=dfn[x] 
25             if(x==root) child++;        //若 x为根 
26         }
27         else if(x!=root) low[x]=min(low[x],dfn[v]);
28     }
29     if(child>=2&&x==root) cut[x]=1;        //x为根的割点判断条件:有2棵及以上的子树 
30 }
31 int main()
32 {
33     scanf("%d%d",&n,&m);
34     for(int u,v,i=1;i<=m;i++){
35         scanf("%d%d",&u,&v);
36         add(u,v),add(v,u);
37     }
38     for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i,i);
39     for(int i=1;i<=n;i++) if(cut[i]) tot++;
40     printf("%d
",tot);
41     for(int i=1;i<=n;i++) if(cut[i]) printf("%d ",i);
42     return 0;
43 }
View Code

Then ,我就找了一道题练手 —— 矿场搭建

(先把这题的思路和注释咕咕咕一下趴)

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #define N 510
 5 using namespace std;
 6 int head[N],dfn[N],low[N],visit[N];
 7 bool cut[N];
 8 long long group,cuts,num,t,m,root,cnt,n,time,ans1,ans2,child;
 9 struct node{
10     int to,next;
11 }edge[N*2];        //无向图注意*2 
12 void add(int x,int y){
13     edge[++cnt].next=head[x];
14     edge[cnt].to=y;
15     head[x]=cnt;
16 }
17 void  tarjan(int x,int root){
18     dfn[x]=low[x]=++time;
19     for(int v,i=head[x];i;i=edge[i].next){
20         v=edge[i].to;
21         if(!dfn[v]){
22             tarjan(v,root);
23             low[x]=min(low[x],low[v]);
24             if(low[v]>=dfn[x]&&x!=root) cut[x]=1;
25             if(x==root) child++;
26         }
27         else if(root!=x) low[x]=min(low[x],dfn[v]);
28     }
29     if(x==root&&child>=2) cut[x]=1;
30 }
31 void dfs(int x){
32     visit[x]=group,++num;
33     for(int v,i=head[x];i;i=edge[i].next){
34         v=edge[i].to;
35         if(cut[v]&&visit[v]!=group){
36             cuts++;
37             visit[v]=group;
38         }
39         else if(!visit[v]) dfs(v);
40     }
41 }
42 int main()
43 {
44     while(scanf("%d",&m)&&m){
45         t++;
46         ans1=0,ans2=1,num=0,time=0,cnt=0,n=0;
47         memset(head,0,sizeof(head));
48         memset(dfn,0,sizeof(dfn));
49         memset(low,0,sizeof(low));
50         memset(cut,0,sizeof(cut));
51         memset(visit,0,sizeof(visit));    //初始化 
52         long long  u,v;
53         
54         for(long long i=1;i<=m;i++){
55             scanf("%lld%lld",&u,&v);
56             add(u,v),add(v,u);        //无向图 
57             n=max(max(v,u),n);        //求点数 
58         }
59             
60         for(long long i=1;i<=n;i++){
61             if(!dfn[i]) child=0,tarjan(i,i);
62             for(int i=1;i<=n;i++){
63                 if(!visit[i]&&!cut[i]){
64                     ++group;
65                     num=cuts=0;
66                     dfs(i);
67                     if(cuts==0) ans1+=2,ans2*=(num-1)*num/2;
68                     else if(cuts==1) ans1++,ans2*=num;//printf("
111
");
69                 }
70             }
71         }
72         printf("Case %lld: %lld %lld
",t,ans1,ans2);
73     }
74     return 0;
75 }
View Code

 
    P1726 上白泽慧音

通过这道题我发现我对tarjan的理解还不够透彻(不知如何变换),代码也没记牢,还需多加练习!

注意 : 字典序的处理

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #define N 5010
 5 #define M 50010
 6 using namespace std;
 7 int read(){
 8     int x=0,f=1; char c=getchar();
 9     while(c<'0'||c>'9') {if(c=='-')f=-1; c=getchar();}
10     while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
11     return x*f;
12 }
13 struct node{
14     int next,to;
15 }edge[M*2];
16 int n,m,cnt,tot,time,id,maxn;
17 int dfn[N],low[N],head[M*2],vis[N],visit[N],stack[N],size[N];
18 void add(int x,int y){
19     edge[++cnt].next=head[x];
20     edge[cnt].to=y;
21     head[x]=cnt;
22 }
23 void tarjan(int x){
24     dfn[x]=low[x]=++time;
25     stack[++id]=x;
26     visit[x]=1;
27     for(int i=head[x];i;i=edge[i].next){
28         if(!dfn[edge[i].to]){
29             tarjan(edge[i].to);
30             low[x]=min(low[x],low[edge[i].to]);
31         }
32         else{
33             if(visit[edge[i].to])
34                 low[x]=min(low[x],dfn[edge[i].to]);
35         }
36     }
37     if(low[x]==dfn[x]){
38         tot++;
39         while(1){
40             vis[stack[id]]=tot;
41             size[tot]++; //printf("%d ",size[tot]);
42             visit[stack[id]]=0,id--;
43             if(x==stack[id+1]) break;
44         }
45     }
46 }
47 int main()
48 {
49     n=read(),m=read();
50     for(int i=1;i<=m;i++){
51         int u=read(),v=read(),t=read();
52         if(t==1) add(u,v);
53         else add(u,v),add(v,u);
54     }
55     
56     for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
57     
58     for(int i=1;i<=tot;i++) maxn=max(maxn,size[i]);//printf("* %d
",size[i]);
59         printf("%d
",maxn);
60     for(int i=1;i<=n;i++){
61         if(size[vis[i]]==maxn){
62             int now=vis[i];
63             for(int j=i;j<=n;j++)
64                 if(vis[j]==now) printf("%d ",j);
65             return 0;
66         }
67     }
68     return 0;
69 }
View Code
原文地址:https://www.cnblogs.com/RR-Jin/p/11617370.html