强连通分量初探 By cellur925

Posted nopartyfoucaodong

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了强连通分量初探 By cellur925相关的知识,希望对你有一定的参考价值。

并不理解。但是毕竟也做了一些题,略微小结。

注:这里讨论的暂时是有向图的强联通分量。

先贴出模板。学长:我也不理解,但我可以叫你们怎么背代码。

 1 #include<cstdio>
 2 #include<algorithm>
 3 #include<stack>
 4 #define maxn
 5 
 6 using namespace std;
 7 int dfn[maxn],low[maxn]
 8 
 9 void dfs(int p)
10 {
11     dfn[p]=low[p]=++dfs_clock;
12     s.push(p);
13     for(int i=head[p];i;i=edge[i].next)
14     {
15         int y=edge[i].to;
16         if(!dfn[y])
17         {
18             dfs(y);
19             low[p]=min(low[p],low[y]);
20         }
21         else if(!scc[y]) low[p]=min(low[p],dfn[y]);
22     }
23     if(dfn[p]==low[p])
24     {
25         scc_cnt++;
26         while(1)
27         {
28             int x=s.top();
29             s.pop();
30             scc[x]=scc_cnt;
31             if(x==p) break;
32         }
33     } 
34 }
35 
36 int main()
37 {
38     scanf("%d",&n);
39     for(int i=1;i<=m;i++)
40      //读入边信息
41     for(int i=1;i<=n;i++)
42     {
43         if(!dfn[i]) dfs(i);
44         tong[scc[i]]++;
45         //scc[i]->i点在哪个强连通分量
46         //tong[i]-> 第i个强连通分量大小 
47     }
48     return 0;
49 } 

一 缩点

一句话来说,就是求出有向图中的强联通分量后,把每个强联通分量用一个点代替,得到一个DAG(有向无环图)。

我们用一个新的邻接表来记录新的DAG上的边。

这个过程可以近似的理解为缩点。先放下求缩点的模板。

 1 #include<cstdio>
 2 #include<algorithm>
 3 #include<stack>
 4 #include<queue>
 5 #include<cstring>
 6 
 7 using namespace std;
 8 //tarjan_need
 9 int n,m,x,y,tot,qwq,ans,scc_cnt,dfs_clock,sum[10090],pv[10090],head[10090],head_DAG[10090],low[10090],dfn[10090],scc[10090];
10 struct node{
11     int next,to;
12 }edge[100090];
13 struct nodee{
14     int next,to;
15 }edge_DAG[100090];
16 stack<int>s;
17 
18 void add(int x,int y,int op)
19 {
20     if(op==1)
21     {
22         edge[++tot].to=y;
23         edge[tot].next=head[x];
24         head[x]=tot;
25     }
26     else if(op==2)
27     {
28         edge_DAG[++qwq].to=y;
29         edge_DAG[qwq].next=head_DAG[x];
30         head_DAG[x]=qwq;
31     }
32 }
33 
34 void tarjan(int p)
35 {
36     dfn[p]=low[p]=++dfs_clock;
37     s.push(p);
38     for(int i=head[p];i;i=edge[i].next)
39     {
40         int y=edge[i].to;
41         if(!dfn[y])
42         {
43             tarjan(y);
44             low[p]=min(low[p],low[y]);
45         }
46         else if(!scc[y]) low[p]=min(low[p],dfn[y]);
47     }
48     if(dfn[p]==low[p])
49     {
50         scc_cnt++;
51         while(1)
52         {
53             int x=s.top();
54             s.pop();
55             scc[x]=scc_cnt;
56             if(x==p) break;
57         }
58     }
59 }
60 
61 int main()
62 {
63     scanf("%d%d",&n,&m);
64     for(int i=1;i<=n;i++) scanf("%d",&pv[i]);
65     for(int i=1;i<=m;i++)
66         scanf("%d%d",&x,&y),add(x,y,1);
67     for(int i=1;i<=n;i++)
68         if(!dfn[i]) tarjan(i);
69     for(int x=1;x<=n;x++)
70         for(int i=head[x];i;i=edge[i].next)
71             {
72                 int y=edge[i].to;
73                 if(scc[x]!=scc[y]) 
74                     add(scc[x],scc[y],2);
75             }
76     return 0;
77 }

然鹅洛谷的模板题更深一步,求:

给定一个n个点m条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。

允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。

我们跑一遍tarjan进行缩点,得到一个新的DAG,在DAG上枚举起点,各跑一遍spfa求最长路,每跑完再枚举终点。注意这里是点带权而不是边带权,在进行强联通分量个数划分的时候,我们需要更新合并每个SCC的点权和(注意用新的数组保存!被这个地方卡了)。

技术分享图片
  1 #include<cstdio>
  2 #include<algorithm>
  3 #include<stack>
  4 #include<queue>
  5 #include<cstring>
  6 
  7 using namespace std;
  8 //tarjan_need
  9 int n,m,x,y,tot,qwq,ans,scc_cnt,dfs_clock,sum[10090],pv[10090],head[10090],head_DAG[10090],low[10090],dfn[10090],scc[10090];
 10 struct node{
 11     int next,to;
 12 }edge[100090];
 13 struct nodee{
 14     int next,to;
 15 }edge_DAG[100090];
 16 stack<int>s;
 17 //spfa_need
 18 int dis[10090];
 19 bool vis[10090];
 20 
 21 void add(int x,int y,int op)
 22 {
 23     if(op==1)
 24     {
 25         edge[++tot].to=y;
 26         edge[tot].next=head[x];
 27         head[x]=tot;
 28     }
 29     else if(op==2)
 30     {
 31         edge_DAG[++qwq].to=y;
 32         edge_DAG[qwq].next=head_DAG[x];
 33         head_DAG[x]=qwq;
 34     }
 35 }
 36 
 37 void tarjan(int p)
 38 {
 39     dfn[p]=low[p]=++dfs_clock;
 40     s.push(p);
 41     for(int i=head[p];i;i=edge[i].next)
 42     {
 43         int y=edge[i].to;
 44         if(!dfn[y])
 45         {
 46             tarjan(y);
 47             low[p]=min(low[p],low[y]);
 48         }
 49         else if(!scc[y]) low[p]=min(low[p],dfn[y]);
 50     }
 51     if(dfn[p]==low[p])
 52     {
 53         scc_cnt++;
 54         while(1)
 55         {
 56             int x=s.top();
 57             s.pop();
 58             scc[x]=scc_cnt;
 59             sum[scc_cnt]+=pv[x];
 60             if(x==p) break;
 61         }
 62     }
 63 }
 64 
 65 void spfa(int start)
 66 {
 67     memset(dis,0,sizeof(dis));
 68     memset(vis,0,sizeof(vis));
 69     queue<int>q;
 70     q.push(start);dis[start]=sum[start];vis[start]=1;
 71     while(!q.empty())
 72     {
 73         int x=q.front();q.pop();vis[x]=0;
 74         for(int i=head_DAG[x];i;i=edge_DAG[i].next)
 75         {
 76             int y=edge_DAG[i].to;
 77             if(dis[y]<dis[x]+sum[y])
 78             {
 79                 dis[y]=dis[x]+sum[y];
 80                 if(!vis[y])
 81                 {
 82                     vis[y]=1;
 83                     q.push(y);
 84                 }
 85             }
 86         }
 87     }
 88     for(int i=1;i<=scc_cnt;i++) ans=max(ans,dis[i]);
 89 }
 90 
 91 int main()
 92 {
 93     scanf("%d%d",&n,&m);
 94     for(int i=1;i<=n;i++) scanf("%d",&pv[i]);
 95     for(int i=1;i<=m;i++)
 96         scanf("%d%d",&x,&y),add(x,y,1);
 97     for(int i=1;i<=n;i++)
 98         if(!dfn[i]) tarjan(i);
 99     for(int x=1;x<=n;x++)
100         for(int i=head[x];i;i=edge[i].next)
101             {
102                 int y=edge[i].to;
103                 if(scc[x]!=scc[y]) 
104                     add(scc[x],scc[y],2);
105             }
106     for(int i=1;i<=scc_cnt;i++) spfa(i);
107     printf("%d",ans);
108     return 0;
109 }
View Code

二、受欢迎的牛

题目描述

每头奶牛都梦想成为牛棚里的明星。被所有奶牛喜欢的奶牛就是一头明星奶牛。所有奶

牛都是自恋狂,每头奶牛总是喜欢自己的。奶牛之间的“喜欢”是可以传递的——如果A喜

欢B,B喜欢C,那么A也喜欢C。牛栏里共有N 头奶牛,给定一些奶牛之间的爱慕关系,请你

算出有多少头奶牛可以当明星。

题意可以再简化: 给定一个有向图,问有多少个顶点是由任何顶点出发都可达的。

分析:(抄lyc课件)

? 有向无环图中唯一出度为 0 的点,一定可以由任何点出发均可达
? 由于无环,所以从任何点出发往前走,必然终止于一个出度为0
的点

? 求出所有强连通分量,每个强连通分量缩成一点,形成一个有向
无环图DAG。
? DAG上面如果恰有一个出度为0的点,说明DAG所有的点可到达这
个点,这个点是原图中的强连通分量,该强连通分量的点数,就
是答案。
? DAG上面如果有不止一个出度为0的点,因为它们不能互相到达,
故原问题无解,答案为0

code

技术分享图片
 1 #include<cstdio>
 2 #include<algorithm>
 3 #include<stack>
 4  
 5 using namespace std;
 6 
 7 int n,m,tot,qwq,Chemist,cellur,dfs_clock,scc_cnt,qaq,qaqaq;
 8 int head[10090],head_DAG[10090],dfn[10090],low[10090],scc[10090],du[10090],tong[10090];
 9 struct node{
10     int to,next;
11 }edge[50090];
12 stack<int>s;
13 
14 void add(int x,int y,int op)
15 {
16     if(op==1)
17     {
18         edge[++tot].to=y;
19         edge[tot].next=head[x];
20         head[x]=tot;
21     }
22 }
23 
24 void dfs(int p)
25 {
26     dfn[p]=low[p]=++dfs_clock;
27     s.push(p);
28     for(int i=head[p];i;i=edge[i].next)
29     {
30         int y=edge[i].to;
31         if(!dfn[y])
32         {
33             dfs(y);
34             low[p]=min(low[p],low[y]);
35         }
36         else if(!scc[y]) low[p]=min(low[p],dfn[y]);
37     }
38     if(dfn[p]==low[p])
39     {
40         scc_cnt++;
41         while(1)
42         {
43             int x=s.top();
44             s.pop();
45             scc[x]=scc_cnt;
46             if(x==p) break;
47         }
48     }
49 }
50 
51 int main()
52 {
53     scanf("%d%d",&n,&m);
54     for(int i=1;i<=m;i++)
55         scanf("%d%d",&Chemist,&cellur),add(Chemist,cellur,1);
56     for(int i=1;i<=n;i++)
57     {
58         if(!dfn[i]) dfs(i);
59         tong[scc[i]]++;
60     }
61     for(int x=1;x<=n;x++)
62         for(int i=head[x];i;i=edge[i].next)
63             {
64                 int y=edge[i].to;
65                 if(scc[x]!=scc[y]) du[scc[x]]++;
66             } 
67     
68     for(int i=1;i<=scc_cnt;i++)
69          if(du[i]==0) qaq++,qaqaq=i;
70          
71     if(qaq==1) printf("%d",tong[qaqaq]);
72     else printf("0");
73     return 0;
74 }
View Code

三、信息传递

题目描述

有 n 个同学(编号为 1 到 n)正在玩一个信息传递的游戏。在游戏里每人都有一个固定的信息传递对象,

其中,编号为 ii 的同学的信息传递对象是编号为 Ti? 的同学。

游戏开始时,每人都只知道自己的生日。之后每一轮中,所有人会同时将自己当前所知的生日信息告诉各自的信息传递对象(注意:可能有人可以从若干人那里获取信息, 但是每人只会把信息告诉一个人,即自己的信息传递对象)。当有人从别人口中得知自 己的生日时,游戏结束。请问该游戏一共可以进行几轮?

 

题意也是比较明白,求图中的最小环,我们可以用tarjan求(虽然有些小题大做的嫌疑)。记录各个强联通分量的大小,最后找最小的大于1的环就行了。

code

技术分享图片
 1 #include<cstdio>
 2 #include<algorithm>
 3 #include<stack>
 4 #define maxn 200090
 5 
 6 using namespace std;
 7 
 8 int n,y,tot,ans=999999,dfs_clock,scc_cnt,tong[maxn],dfn[maxn],low[maxn],head[maxn],scc[maxn]; 
 9 stack<int>s;
10 struct node{
11     int to,next;
12 }edge[maxn];
13 
14 void add(int x,int y)
15 {
16     edge[++tot].to=y;
17     edge[tot].next=head[x];
18     head[x]=tot;
19 }
20 
21 void dfs(int p)
22 {
23     dfn[p]=low[p]=++dfs_clock;
24     s.push(p);
25     for(int i=head[p];i;i=edge[i].next)
26     {
27         int y=edge[i].to;
28         if(!dfn[y])
29         {
30             dfs(y);
31             low[p]=min(low[p],low[y]);
32         }
33         else if(!scc[y]) low[p]=min(low[p],dfn[y]);
34     }
35     if(dfn[p]==low[p])
36     {
37         scc_cnt++;
38         while(1)
39         {
40             int x=s.top();
41             s.pop();
42             scc[x]=scc_cnt;
43             if(x==p) break;
44         }
45     }
46 }
47 
48 int main()
49 {
50     scanf("%d",&n);
51     for(int i=1;i<=n;i++)
52      scanf("%d",&y),add(i,y);
53     for(int i=1;i<=n;i++)
54     {
55         if(!dfn[i]) dfs(i);
56         tong[scc[i]]++;
57     }
58     for(int i=1;i<=scc_cnt;i++) if(tong[i]>1&&tong[i]<ans) ans=tong[i];
59     printf("%d
",ans);
60     return 0;
61 }
View Code

 









以上是关于强连通分量初探 By cellur925的主要内容,如果未能解决你的问题,请参考以下文章

字符串最小表示初探 By cellur925

二分图最大匹配初探 By cellur925

第二类Stirling数初探 By cellur925

poj3417 闇の連鎖 树上差分By cellur925

LuoguP2700逐个击破并查集/生成树/正难则反By cellur925

强连通分量算法·$tarjan$初探