• 【强连通分量】


    有向图的强连通分量

    一.定义

             给定一张有向图。若对于任意两个节点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 }
  • 相关阅读:
    VMWare上的ubuntu系统安装VMWare Tools(图文)
    Ubuntu添加新分区
    emacs入门
    SQL UNION 操作符
    eclipse安装其他颜色主题包
    mysql左连接
    不能用notepad++编辑器编写python
    ImportError: No module named simplejson.scanner
    运行 python *.py 文件出错,如:python a.py
    doc命令大全(详细版)
  • 原文地址:https://www.cnblogs.com/lirh04/p/12221949.html
Copyright © 2020-2023  润新知