分析强连通的主要解体思路(基础篇)

                                                强连通的主要考查问题分析


  由于,本人也是刚入门不久。所以,难免有些地方说的不对,希望说本人说的不对的地方,欢迎大家提出。

  强连通的基础考查有给你一个又向图叫你判断改图是否是强连通图,还有的就是ACM竞赛中最常用的缩点求解强连通。这次我主要讲缩点求解强连通。还有更难得混合题和变形题,我就不讲解了。

  强连通的定义:强连通的本质解释就是“相互可达”的关系。

  强连通的缩点:每一个集合成为又向图的一个强连通分量(Strongly Connected Component,SCC)。如果把一个集合看成一个点,那么所有SCC构成了一个SCC图。这个SCC图不会存在又向环,因此是一个DAG(有向无环图)。

  求解强连通有三种算法,但是常用的只有Kosaraju和Tarjan算法。因此,我主要以这两种算法为主给大家讲解,就不再涉及第三种算法了,有兴趣的可以自己上网查找。

Kosaraju算法: 先把SCC图拓扑排序,然后按照拓扑的逆序进行遍历(为了防止多个SCC混在一起)。

Tarjan算法: 判断一个点是否为一个SCC中最早被发现的点。

   其实,Kosaraju算法的本质思想就是,一个个建立SCC.而Tarjan算法则是在一张大的SCC图中分出小的SCC子图。


     两个模板有空的时候在给出!!!






然后再给出两道基础练习题。

题目链接:Click Here~

题目分析:

   改题的本质理解我也是,看了一个大牛的博客才理解的。他解释的非常的好,因此,我就直接复制他的文章了。在这里我就不在赘述了。

博客链接:Click Here!

  强连通分量缩点求入度为0的个数和出度为0的分量个数

题目大意:N(2<N<100)各学校之间有单向的网络,每个学校得到一套软件后,可以通过单向网络向周边的学校传输,问题1:初始至少需要向多少个学校发放软件,使得网络内所有的学校最终都能得到软件。2,至少需要添加几条传输线路(边),使任意向一个学校发放软件后,经过若干次传送,网络内所有的学校最终都能得到软件。

也就是:

给定一个有向图,求:

1) 至少要选几个顶点,才能做到从这些顶点出发,可以到达全部顶点

 

2) 至少要加多少条边,才能使得从任何一个顶点出发,都能到达全部顶点

 

—顶点数<= 100

解题思路:

—        1. 求出所有强连通分量

—        2. 每个强连通分量缩成一点,则形成一个有向无环图DAG。

—        3. DAG上面有多少个入度为0的顶点,问题1的答案就是多少

在DAG上要加几条边,才能使得DAG变成强连通的,问题2的答案就是多少

加边的方法:

要为每个入度为0的点添加入边,为每个出度为0的点添加出边

假定有 n 个入度为0的点,m个出度为0的点,如何加边?

把所有入度为0的点编号 0,1,2,3,4 ....N -1

每次为一个编号为i的入度为0点的顶点可达到出度0点的顶点,添加一条出边,连到编号为(i+1)%N 的那个出度0点,

这需要加n条边

若 m <= n,则

加了这n条边后,已经没有入度0点,则问题解决,一共加了n条边

若 m > n,则还有m-n个出度0点,则从这些点以外任取一点,和这些点都连上边,即可,这还需加m-n条边。

所以,max(m,n)就是第二个问题的解

此外:当只有一个强连通分支的时候,就是缩点后只有一个点,虽然入度出度为0的都有一个,但是实际上不需要增加清单的项了,所以答案是1,0;


#include <iostream>
#include <algorithm>
#include <vector>
#include <stack>
#include <cstdio>
#include <cstring>
using namespace std;

const int N = 100 + 5;
vector<int> G[N];
int in[N],out[N];
int pre[N],lowlink[N],sccno[N],dfs_clock,scc_cnt;
stack<int> S;
void Init()
{
    for(int i = 0;i < N;++i) G[i].clear();
    while(!S.empty())S.pop();
}
void dfs(int u)
{
    pre[u] = lowlink[u] = ++dfs_clock;
    S.push(u);
    for(int i = 0;i < G[u].size();++i){
       int v = G[u][i];
       if(!pre[v]){
          dfs(v);
          lowlink[u] = min(lowlink[u],lowlink[v]);
       }
       else
       if(!sccno[v]){
          lowlink[u] = min(lowlink[u],pre[v]);
       }
    }
    if(lowlink[u] == pre[u]){
       scc_cnt++;
       int x;
       do
       {
           x = S.top();
           S.pop();
           sccno[x] = scc_cnt;
       }while(x != u);
    }
}
void find_scc(int n)
{
    dfs_clock = scc_cnt = 0;
    memset(sccno,0,sizeof(sccno));
    memset(pre,0,sizeof(pre));
    for(int i = 1;i <= n;++i){
      if(!pre[i])dfs(i);
    }
}
int main()
{
    int n,edge;
    while(~scanf("%d",&n))
    {
        Init();
        for(int i = 1;i <= n;++i){
           while(scanf("%d",&edge),edge)
              G[i].push_back(edge);
        }
        find_scc(n);
        for(int i = 1;i <= scc_cnt;++i)
          in[i] = out[i] = 1;
        for(int u = 1;u <= n;++u)
          for(int i = 0;i < G[u].size();++i){
             int v = G[u][i];
             if(sccno[u] != sccno[v])
                in[sccno[v]] = out[sccno[u]] = 0;
          }
          int a = 0,b = 0;
          for(int i = 1;i <= scc_cnt;++i){
             if(in[i]) a++;
             if(out[i]) b++;
          }
          int ans = max(a,b);
          if(scc_cnt == 1)ans = 0,a = 1;
          printf("%d\n%d\n",a,ans);
    }
    return 0;
}


题目链接:Click Here~

题目分析:

   在数学中,我们常常需要完成若干个命题的等价性证明。比如,有4个命题a,b,c,d,我们证明a<->b,然后b<->c,最后c<->d。注意每次都是双向的,因此一共完成了次推导。另一种方法是证明a->b,然后b->c,接着c->d,最后d->a,只需四次。现在你的任务是证明n个命题全部等价,且你的朋友已经为你做出了m次推到(已经知道推导的内容),你至少还需要做几次次推导才能完成整个证明?

   原型跟上体差不多,不懂得自己看代码:

#include <iostream>
#include <algorithm>
#include <vector>
#include <stack>
#include <cstdio>
#include <cstring>
using namespace std;

const int N = 2e4 + 5;
vector<int> G[N];
stack<int> S;
int in[N],out[N];
int dfs_clock,scc_cnt,lowlink[N],sccno[N],vst[N],pre[N];

void dfs(int u)
{
    lowlink[u] = pre[u] = ++dfs_clock;
    S.push(u);
    for(int i = 0;i < G[u].size();++i){
       int v = G[u][i];
       if(!pre[v]){
          dfs(v);
          lowlink[u] = min(lowlink[u],lowlink[v]);
       }
       else
       if(!sccno[v])
          lowlink[u] = min(lowlink[u],pre[v]);
    }
    if(lowlink[u] == pre[u]){
       scc_cnt++;
       int x;
       do
       {
           x = S.top();
           S.pop();
           sccno[x] = scc_cnt;
       }while(x != u);
    }
}
void find_scc(int n)
{
   dfs_clock = scc_cnt = 0;
   memset(lowlink,0,sizeof(lowlink));
   memset(sccno,0,sizeof(sccno));
   memset(pre,0,sizeof(pre));
   memset(vst,0,sizeof(vst));
   for(int i = 1;i <= n;++i)
     if(!pre[i])dfs(i);
}
int main()
{
//    freopen("Input.txt","r",stdin);
    int T,n,m;
    scanf("%d",&T);
    while(T--)
    {

        int a,b;
        scanf("%d%d",&n,&m);
        for(int i = 0;i <= n;++i)G[i].clear();
        for(int i = 0;i < m;++i){
           scanf("%d%d",&a,&b);
           G[a].push_back(b);
        }
        find_scc(n);
        for(int i = 1;i <= scc_cnt;++i)
           in[i] = out[i] = 1;
        for(int u = 1;u <= n;++u)
          for(int i = 0;i < G[u].size();++i){
             int v = G[u][i];
             if(sccno[u] != sccno[v])
                in[sccno[v]] = out[sccno[u]] = 0;
          }
        a = b = 0;
        for(int i = 1;i <= scc_cnt;++i){
           if(in[i])a++;
           if(out[i])b++;
        }
        int ans = max(a,b);
        if(scc_cnt == 1)ans = 0;
        printf("%d\n",ans);
    }
    return 0;
}

 


 


  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值