强连通分量

Posted lirh04

tags:

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

有向图的强连通分量

一.定义

         给定一张有向图。若对于任意两个节点x,y 既存在从x->y的路径,也存在从y->x的路径,则称该有向图为“强连通图”。

          有向图的极大连通子图被称为强连通分量

二.强连通分量的求法。

          1.Tarjan算法

                    基于 dfs 的一种算法,每一个强连通分量为其搜索树的一棵子树,并且需要用到 的数据结构。

                    tarjan 中两个重要的数组  dfn[]时间戳,搜索时的序号) , low[] (表示当前能回溯到的最小的时间戳), 初始化  dfn[] = low[] = ++ cnt;

                    运算过程:

                                (1). main 函数里面用 for 循环 查找 ,若dfn[]为0,则进行 tarjan 算法。

                                (2).  设 v[] 数组表示是否进栈,设 栈 sta[];

                                (3). 对于每个搜索的 x ,对其相连的边进行搜索,若没有,递归运行,若已经入栈,说明形成了 环 ,更新 low 值;、

                                (4). 不断深搜的过程中,若出边的遍历完了,进行回溯,不断比较 low 值,取最小值 , 若dfn[] == low[] ,则找到了 一个强连通分量的根,然后对栈进行弹出操作,直到x 被弹出栈;

code

 1 inline void tarjan(int x){
 2     dfn[x]=++ind;
 3     low[x]=ind;
 4     s[++top]=x;
 5     v[x]=1;
 6     for(int i=head[x];i;i=next[i]){
 7         int y=ver[i];
 8         if(!dfn[y]){
 9             tarjan(y);
10             low[x]=min(low[y],low[x]);
11         }
12         else if(v[y]==1){
13             low[x]=min(low[x],dfn[y]);
14         }
15     }
16     if(low[x]==dfn[x]){
17         int y;
18         do{
19             y=s[top--];
20             v[y]=0;
21         }while(y != x);
22     }
23 }    

              2.Kosaraju 算法

                           算法流程:

                                         (1)存图时需要再存一个反图 

                                         (2) 先在原图上 进行 dfs ,不断访问节点,在回溯前进行后序遍历标号

                                     (3) 第二次进行dfs 时,需用到反图,以标号最大的顶点为起点进行遍历,遍历完后,由dfs 遍历过的顶点集合就是一个强连通分量。

                                       p.s: 要对集合进行分类标记,每找到一个新的起点就要对 标记值进行自增;

code           

 1 int k=0;
 2 inline void dfs(int x){
 3     v[x]=1;
 4     for(int i=0;i<g[x].size();i++){
 5         if(!v[g[x][i]]) dfs(g[x][i]); 
 6     }
 7     vs.push_back(x);
 8 }
 9 inline void rdfs(int x){
10     v[x]=1;
11     fa[x]=k;
12     for(int i=0;i<rg[x].size();i++){
13         if(!v[rg[x][i]]) rdfs(rg[x][i]);
14      } 
15 }
16 inline void work(){
17     for(int i=1;i<=n;i++){
18         if(!v[i]) dfs(i);
19     }
20     memset(v,0,sizeof(v));
21     for(int i=vs.size()-1;i>=0;i--){
22         if(!v[vs[i]]) {
23             k++;
24             rdfs(vs[i]);
25         }
26     }
27 }

 三.例题e.g.

No.1.受欢迎的牛[HAOI2006](来源:洛谷)

题目描述

每头奶牛都梦想成为牛棚里的明星。被所有奶牛喜欢的奶牛就是一头明星奶牛。所有奶牛都是自恋狂,每头奶牛总是喜欢自己的。奶牛之间的“喜欢”是可以传递的——如果A喜欢B,B喜欢C,那么A也喜欢C。牛栏里共有N 头奶牛,给定一些奶牛之间的爱慕关系,请你算出有多少头奶牛可以当明星。

输入格式

第一行:两个用空格分开的整数:N和M

第二行到第M + 1行:每行两个用空格分开的整数:A和B,表示A喜欢B

输出格式

第一行:单独一个整数,表示明星奶牛的数量

输入输出样例

输入 #1
3 3
1 2
2 1
2 3
输出 #1
1

说明/提示

只有 3 号奶牛可以做明星

【数据范围】

10%的数据N<=20, M<=50
30%的数据N<=1000,M<=20000
70%的数据N<=5000,M<=50000
100%的数据N<=10000,M<=50000

 

 

 思路:

        看图

技术图片

          可以得出, b,c,d   为强连通分量,可知,分量中任意一个点被喜欢,则分量中所有点都会被喜欢,所以将图中的所有强连通分量看作一个点,则图变成一个有向无环图,且成为明星   的牛一定是出度为零的点 。

code          

 

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int N=50001 * 2;
 5 int n,m,k=0;
 6 int head[N],next[N],ver[N],tot;
 7 int f[N],d[N],out[N],an[N];
 8 
 9 int find(){
10     int ans=0;
11     for(int i=1;i<=k;i++){
12         for(int j=head[f[i]];j;j=next[j]){
13             int y=ver[j];
14             if(!d[y]) ans++;
15         }
16     }
17     return ans;
18 }
19 
20 inline void add(int x,int y){
21     ver[++tot]=y;
22     next[tot]=head[x];
23     head[x]=tot;
24 }
25 
26 int s[N],top=0;
27 int v[N];
28 int dfn[N],low[N],ind=0;
29 int belong[N],cnt=0;
30 
31 inline void tarjan(int x){
32     dfn[x]=++ind;
33     low[x]=ind;
34     s[++top]=x;
35     v[x]=1;
36     for(int i=head[x];i;i=next[i]){
37         int y=ver[i];
38         if(!dfn[y]){
39             tarjan(y);
40             low[x]=min(low[y],low[x]);
41         }
42         else if(v[y]==1){
43             low[x]=min(low[x],dfn[y]);
44         }
45     }
46     if(low[x]==dfn[x]){
47         ++cnt;
48         int y;
49         do{
50             y=s[top--];
51             v[y]=0;
52             f[++k]=y;
53             d[y]=1;
54             an[cnt]++;
55         }while(y!=x);
56         out[cnt]=find();
57         k=0;
58         memset(d,0,sizeof(d));
59     }
60 }    
61 
62 inline int read(){//快读
63     int ans=0,f=1;
64     char s=getchar();
65     while(!isdigit(s)) f*=(s==-)? -1:1,s=getchar();
66     do ans=(ans<<1)+(ans<<3)+(s^48),s=getchar();
67     while(isdigit(s));
68     return ans*f;
69 }
70 
71 int main(){
72     n=read(),m=read();
73     for(int i=1;i<=m;i++){
74         int a=read(),b=read();
75         add(a,b);
76     }
77     for(int i=1;i<=n;i++)
78        if(!dfn[i]) tarjan(i);
79     int s=0,ans;
80     for(int i=1;i<=cnt;i++){
81         if(!out[i]) s++,ans=i;
82     }
83     if(s==1) printf("%d",an[ans]);
84     else printf("0");
85     return 0;
86 } 

 No.2  稳定婚姻(来源:洛谷)

 

题目描述

 

我国的离婚率连续7年上升,今年的头两季,平均每天有近5000对夫妇离婚,大城市的离婚率上升最快,有研究婚姻问题的专家认为,是与简化离婚手续有关。

25岁的姗姗和男友谈恋爱半年就结婚,结婚不到两个月就离婚,是典型的“闪婚闪离”例子,而离婚的导火线是两个人争玩电脑游戏,丈夫一气之下,把电脑炸烂。

有社会工作者就表示,80后求助个案越来越多,有些是与父母过多干预有关。而根据民政部的统计,中国离婚五大城市首位是北京,其次是上海、深圳,广州和厦门,那么到底是什么原因导致我国成为离婚大国呢?有专家分析说,中国经济急速发展,加上女性越来越来越独立,另外,近年来简化离婚手续是其中一大原因。

——以上内容摘自第一视频门户

现代生活给人们施加的压力越来越大,离婚率的不断升高已成为现代社会的一大问题。而其中有许许多多的个案是由婚姻中的“不安定因素”引起的。妻子与丈夫吵架后,心如绞痛,于是寻求前男友的安慰,进而夫妻矛盾激化,最终以离婚收场,类似上述的案例数不胜数。

我们已知n对夫妻的婚姻状况,称第i对夫妻的男方为Bi,女方为Gi。若某男Bi与某女Gj曾经交往过(无论是大学,高中,亦或是幼儿园阶段,i≠j),则当某方与其配偶(即Bi与Gi或Bj与Gj)感情出现问题时,他们有私奔的可能性。不妨设Bi和其配偶Gi感情不和,于是Bi和Gj旧情复燃,进而Bj因被戴绿帽而感到不爽,联系上了他的初恋情人Gk……一串串的离婚事件像多米诺骨牌一般接踵而至。若在Bi和Gi离婚的前提下,这2n个人最终依然能够结合成n对情侣,那么我们称婚姻i为不安全的,否则婚姻i就是安全的。

给定所需信息,你的任务是判断每对婚姻是否安全。

 

输入格式

 

第一行为一个正整数n,表示夫妻的对数;

以下n行,每行包含两个字符串,表示这n对夫妻的姓名(先女后男),由一个空格隔开;

第n+2行包含一个正整数m,表示曾经相互喜欢过的情侣对数;

以下m行,每行包含两个字符串,表示这m对相互喜欢过的情侣姓名(先女后男),由一个空格隔开。

 

输出格式

 

输出文件共包含n行,第i行为“Safe”(如果婚姻i是安全的)或“Unsafe”(如果婚姻i是不安全的)。

 

输入输出样例

 

输入 #1
2
Melanie Ashley
Scarlett Charles
1
Scarlett Ashley
输出 #1
Safe
Safe

 

输入 #2
2
Melanie Ashley
Scarlett Charles
2
Scarlett Ashley
Melanie Charles
输出 #2
Unsafe
Unsafe

 

说明/提示

 

对于20%的数据,n≤20;

对于40%的数据,n≤100,m≤400;

对于100%的数据,所有姓名字符串中只包含英文大小写字母,大小写敏感,长度不大于8,保证每对关系只在输入文件中出现一次,输入文件的最后m行不会出现未在之前出现过的姓名,这2n个人的姓名各不相同,1≤n≤4000,0≤m≤20000。

 

 

思路:

         我们可以将题目中的男女关系进行建图,然后求出强连通分量,判断夫妻之间是否在同一强连通分量中,若在,则不安全,反之则安全。

        可以将 夫妻 关系 建为    female -> male ; 将 情人 关系建为 male-> female;

         p.s. 由于原题中需要输入字符串 (string), 我们自己建立映射条件极为不方便 ,于是可以调用数据结构 map :map <string, int> g 进行映射

        因为一共2*n个人 所以建图时 第 i 对夫妻,female 为 i 号节点 ,male 为i+n 号;

code

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int N=30001;
 5 map <string, int> g;
 6 int n,m;
 7 int head[N],next[N],ver[N],tot;
 8 
 9 inline void add(int x,int y){
10     ver[++tot]=y;
11     next[tot]=head[x];
12     head[x]=tot;
13 }
14 
15 int s[N],top=0;
16 int v[N];
17 int dfn[N],low[N],ind=0;
18 int belong[N],cnt=0;
19 
20 inline void tarjan(int x){
21     dfn[x]=++ind;
22     low[x]=ind;
23     s[++top]=x;
24     v[x]=1;
25     for(int i=head[x];i;i=next[i]){
26         int y=ver[i];
27         if(!dfn[y]){
28             tarjan(y);
29             low[x]=min(low[y],low[x]);
30         }
31         else if(v[y]==1){
32             low[x]=min(low[x],dfn[y]);
33         }
34     }
35     if(low[x]==dfn[x]){
36         ++cnt;
37         do{
38             belong[s[top]]=cnt;
39             v[s[top]]=0;
40         }while(s[top--]!=x);
41     }
42 }    
43 
44 inline int read(){
45     int ans=0,f=1;
46     char s=getchar();
47     while(!isdigit(s)) f*=(s==-)? -1:1,s=getchar();
48     do ans=(ans<<1)+(ans<<3)+(s^48),s=getchar();
49     while(isdigit(s));
50     return ans*f;
51 }
52 
53 int main(){
54     n=read();
55     string fe,ma;
56     for(int i=1;i<=n;i++){
57         cin>>fe>>ma;
58         g[fe]=i;
59         g[ma]=i+n;
60         add(i,i+n);
61     } 
62     m=read();
63     for(int i=1;i<=m;i++){
64         cin>>fe>>ma;
65         add(g[ma],g[fe]);
66     }
67     for(int i=1;i<=n*2;i++) if(dfn[i]==0) tarjan(i);
68     for(int i=1;i<=n;i++){
69         if(belong[i]==belong[i+n]) printf("Unsafe
");
70         else printf("Safe
");    
71     }
72     return 0;
73 } 

 

No.3 消息扩散(来源:洛谷)

 

题目描述

有n个城市,中间有单向道路连接,消息会沿着道路扩散,现在给出n个城市及其之间的道路,问至少需要在几个城市发布消息才能让这所有n个城市都得到消息。

输入格式

第一行两个整数n,m表示n个城市,m条单向道路。

以下m行,每行两个整数b,e表示有一条从b到e的道路,道路可以重复或存在自环。

输出格式

一行一个整数,表示至少要在几个城市中发布消息。

输入输出样例

输入 #1
5 4
1 2
2 1
2 3
5 1
输出 #1
2

说明/提示

【数据范围】

对于20%的数据,n≤200;

对于40%的数据,n≤2,000;

对于100%的数据,n≤100,000,m≤500,000.

【限制】

时间限制:1s,内存限制:256M

【注释】

样例中在4,5号城市中发布消息。

 

思路:     

           题目中有重边和自环,那就判断不是重边与自环才建图,然后求强连通分量,入度为0 的顶点个数即为answer;

code             

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int N = 100005;
 4 const int M = 500005;
 5 int x,y,n,m,pointnum,tot,Ind,top,ans;
 6 int head[N],nxt[M],to[M];
 7 int low[N],dfn[N],s[N];
 8 int belong[N],rd[N];
 9 bool in[N];
10 inline int read() {
11     int ans=0,f=1;
12     char s=getchar();
13     while(!isdigit(s)) f*=(s==-)? -1:1,s=getchar();
14     do ans=(ans<<1)+(ans<<3)+(s^48),s=getchar();
15     while(isdigit(s));
16     return ans*f;
17 }
18 
19 inline void add(int x,int y) {
20     nxt[++tot]=head[x];
21     head[x]=tot;
22     to[tot]=y;
23 }
24 
25 inline void dfs(int x) {
26     low[x]=dfn[x]=++Ind;
27     s[++top]=x;
28     in[x]=true;
29     for(int i=head[x]; i; i=nxt[i]) {
30         int v=to[i];
31         if(!dfn[v]) {
32             dfs(v);
33             low[x]=min(low[x],low[v]);
34         } else if(in[v]) low[x]=min(low[x],dfn[v]);
35     }
36     if(low[x]==dfn[x]) {
37         ++pointnum;
38         int j=-1;
39         while(j!=x) {
40             j=s[top--];
41             belong[j]=pointnum;
42             in[j]=false;
43         }
44     }
45 }
46 
47 int main() {
48     n=read(),m=read();
49     for(int i=1; i<=m; i++) {
50         x=read(),y=read();
51         if(x!=y) add(x,y);
52     }
53     for(int i=1; i<=n; i++) if(!dfn[i]) dfs(i);
54     for(int i=1; i<=n; i++)
55         for(int e=head[i]; e; e=nxt[e])
56             if(belong[i]!=belong[to[e]])
57                 rd[belong[to[e]]]++;
58     for(int i=1; i<=pointnum; i++)
59         if(!rd[i]) ans++;
60     cout<<ans<<endl;
61     return 0;
62 }

 

No.4 间谍网络(来源:洛谷)

题目描述

由于外国间谍的大量渗入,国家安全正处于高度的危机之中。如果A间谍手中掌握着关于B间谍的犯罪证据,则称A可以揭发B。有些间谍收受贿赂,只要给他们一定数量的美元,他们就愿意交出手中掌握的全部情报。所以,如果我们能够收买一些间谍的话,我们就可能控制间谍网中的每一分子。因为一旦我们逮捕了一个间谍,他手中掌握的情报都将归我们所有,这样就有可能逮捕新的间谍,掌握新的情报。

我们的反间谍机关提供了一份资料,包括所有已知的受贿的间谍,以及他们愿意收受的具体数额。同时我们还知道哪些间谍手中具体掌握了哪些间谍的资料。假设总共有n个间谍(n不超过3000),每个间谍分别用1到3000的整数来标识。

请根据这份资料,判断我们是否有可能控制全部的间谍,如果可以,求出我们所需要支付的最少资金。否则,输出不能被控制的一个间谍。

输入格式

第一行只有一个整数n。

第二行是整数p。表示愿意被收买的人数,1≤p≤n。

接下来的p行,每行有两个整数,第一个数是一个愿意被收买的间谍的编号,第二个数表示他将会被收买的数额。这个数额不超过20000。

紧跟着一行只有一个整数r,1≤r≤8000。然后r行,每行两个正整数,表示数对(A, B),A间谍掌握B间谍的证据。

输出格式

如果可以控制所有间谍,第一行输出YES,并在第二行输出所需要支付的贿金最小值。否则输出NO,并在第二行输出不能控制的间谍中,编号最小的间谍编号。

输入输出样例

输入 #1
3
2
1 10
2 100
2
1 3
2 3
输出 #1
YES
110
输入 #2
4
2
1 100
4 200
2
1 2
3 4
输出 #2
NO
3

思路:                                                                                                                                  

           不难发现有两种情况:

                               (1). 此间谍没人能够揭发他,也不能够贿赂他,那么直接输出他的编号;

                               (2).   1) 没有环,则资金为没有入度那个点的钱数;

                                             2)有环,则在环中找最小值;

code:

 

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 struct ss{
 4     int next,to;
 5 };ss data[200010];
 6 const int inf=1e9+7;
 7 int n,q,timeclock,p,top,cnt,ans,r;
 8 int dfn[200010],low[200010],s[200010],instack[200010],next[200010],head[200010];
 9 int belong[200010],money[200010],sum[200010],size[200010],rd[200010];
10 void add(int a,int b)
11 {
12     data[++p].next=head[a];
13     data[p].to=b;
14     head[a]=p;
15 }
16 void tarjan(int a)          
17 {
18     dfn[a]=low[a]=++timeclock;
19     instack[a]=1;
20     s[++top]=a;
21     for(int i=head[a];i;i=data[i].next)
22     {
23         int v=data[i].to;
24         if(!dfn[v])
25         {
26             tarjan(v);
27             low[a]=min(low[a],low[v]);
28         }
29         else
30         if(instack[v])
31         low[a]=min(low[a],dfn[v]);
32     }
33     if(dfn[a]==low[a])
34     {
35         cnt++;
36         while(s[top+1]!=a)
37         {
38             belong[s[top]]=cnt;
39             instack[s[top]]=0;
40             size[cnt]++;
41             sum[cnt]=min(sum[cnt],money[s[top]]);
42             top--;
43         }
44     }
45 }
46 int main()
47 {
48     scanf("%d",&n);
49     for(int i=1;i<=n;i++)
50     money[i]=1e9+7;       
51     for(int i=1;i<=n;i++)
52     sum[i]=1e9;
53     scanf("%d",&q);
54     for(int i=1;i<=q;i++)
55     {
56         int u,mo;
57         scanf("%d%d",&u,&mo);
58         money[u]=mo;
59     }
60     scanf("%d",&r);
61     for(int i=1;i<=r;i++)
62     {
63         int u,v;
64         scanf("%d%d",&u,&v);
65         add(u,v);
66     }
67     for(int i=1;i<=n;i++)
68     if(!dfn[i]&&money[i]!=inf)   
69     tarjan(i);
70     for(int i=1;i<=n;i++)      
71     if(!dfn[i])
72     {
73         printf("NO
");
74         printf("%d
",i);
75         return 0;
76     }
77 
78     for(int i=1;i<=n;i++)
79     for(int j=head[i];j;j=data[j].next)
80     if(belong[i]!=belong[data[j].to])
81     {
82         rd[belong[data[j].to]]++;   
83     }
84     printf("YES
");
85     for(int i=1;i<=cnt;i++)
86     {
87        if(!rd[i])
88        { 
89            ans+=sum[i];
90        }
91     }
92     printf("%d
",ans);
93     return 0;
94 }

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

HDU 1269 迷宫城堡 tarjan算法求强连通分量

Kosaraju算法——强连通分量

求有向图的强连通分量的算法

HDU 3836 Equivalent Sets(强连通分量)

强连通分量

USACO network of school 强连通分量