ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

【强连通分量】

2020-01-21 21:00:23  阅读:268  来源: 互联网

标签:连通 间谍 int top ++ dfn low 分量


有向图的强连通分量

一.定义

         给定一张有向图。若对于任意两个节点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\n");
70         else printf("Safe\n");    
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\n");
74         printf("%d\n",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\n");
85     for(int i=1;i<=cnt;i++)
86     {
87        if(!rd[i])
88        { 
89            ans+=sum[i];
90        }
91     }
92     printf("%d\n",ans);
93     return 0;
94 }

标签:连通,间谍,int,top,++,dfn,low,分量
来源: https://www.cnblogs.com/lirh04/p/12221949.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有