有向强连通分量(SCC)

简介: 有向强连通分量(SCC)

对于一个有向图,连通分量:对于分量中任意两点u,v,必然可以从u到v,从v到u

强连通分量:最大连通分量

作用:将任意一个有向图缩点(将所有连通分量缩成一个点)变成有向无环图(拓扑图)


tarjan模板

void tarjan(int u)
{
    dfn[u]=low[u]===timestamp;
    stk[++top]=u,in_stk[u]=true;
    for(int i=h[u];~i;i=ne[i])
    {
        int j=e[i];
        if(!dfn[j])
        {
            tarjan(j);
            low[u]=min(low[u],low[j]);
        }
        else if(in_stk[j]) low[u]=min(low[u],dfn[j]);
    }
    if(dfn[u]==low[u])
    {
        int y;
        ++scc_cnt;//连通分量的个数
        do
        {
            y=stk[top--];
            in_stk[y]=false;
            id[y]=scc_cnt;
        }while(y!=u);
    }
}

缩点(把强连通分量缩成一个点)

一般做法:

1.tarjan

2.缩点

3.拓扑序递归求解

1.受欢迎的牛

信息学奥赛一本通(C++版)在线评测系统 (ssoier.cn)


1.tarjan

2.缩点

3.枚举求答案

当操作了tarjan和缩点之后变成的拓扑图,假如有两个出度为0的点说明,这两个点互不可达,说明没有一头牛受任何牛欢迎

假如有一个出度为0的点,说明这个点就是受所有牛欢迎的

假如只有一个连通分量说明连通分量里的每头牛都互相受欢迎,则+连通分量牛的数量

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e4+10,M=5e4+10;
int n,m;
int h[N],e[M],ne[M],idx;
int dfn[N],low[N],timestamp;//dfn是第一次遍历的时间戳,low是以当前点往下走的最早的时间戳
int stk[N],top;
bool in_stk[N];//标记是否在栈中
int id[N],scc_cnt,size[N];//id表示每个点属于强连通分量那个编号,scc_cnt表示强连通分量的个数,size表示每个强连通分量点的数量
int dout[N];//记录每个缩完点之后的出度是多少
void add(int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void tarjan(int u)
{
    low[u]=dfn[u]=++timestamp;//让他们都等于时间戳
    stk[++top]=u,in_stk[u]=true;
    for(int i=h[u];~i;i=ne[i])
    {
        int j=e[i];
        if(!dfn[j])//假如没有遍历过
        {
            tarjan(j);
            low[u]=min(low[u],low[j]);
        }
        else if(in_stk[j]) low[u]=min(low[u],dfn[j]);
    }
    if(dfn[u]==low[u])
    {
        ++scc_cnt;//连通分量++
        int y;
        do
        {
            y=stk[top--];
            in_stk[y]=false;//不在栈中
            id[y]=scc_cnt;//让这个点的编号等于这个连通分量
            size[scc_cnt]++;//这个连通分量数量++
        }while(y!=u);
    }
}
int main()
{
    memset(h,-1,sizeof h);
   cin>>n>>m;
   while(m--)
   {
       int a,b;
       cin>>a>>b;
       add(a,b);
   }
   for(int i=1;i<=n;i++)//tarjan求强连通分量
     if(!dfn[i])//假如没有遍历过
        tarjan(i);
   for(int u=1;u<=n;u++)//缩点
      for(int i=h[u];~i;i=ne[i])
      {
          int j=e[i];
          int a=id[u],b=id[j];
          if(a!=b) dout[a]++;//假如不在一个连通分量里,则a在的连通分量出度++
      }
   int zeros=0,sum=0;
   for(int i=1;i<=scc_cnt;i++)
      if(!dout[i])
      {
          zeros++;
          sum+=size[i];//假如连通分量的个数
          if(zeros>1)//假如有多个连通分量的出度等于0,说明没有任何一头牛受欢迎
          {
              sum=0;
              break;
          }
      }
 cout<<sum<<endl;
    return 0;
}

2.学校网络

367. 学校网络 - AcWing题库


1.tarjan


2.缩点


3.求入度与出度


入度为0的点是起点,出度为0的点是终点


最少给的软件就是给起点


建立的关系就起点与终点的最大值证明如下:


设起点为n个,终点有m个,并且设n<=m


1.当n==1时,直接把所有终点与起点相连就是所有都连通了则为m


2.当n>1时,我们可以让一个终点与起点相连,这样终点与起点都-1,直到起点只有1个时,我们已经连了n-1对起点与终点,此时起点只有一个,终点变成m-(n-1)个,这是根据1情况要连m-(n-1)个,加上刚刚连的n-1条起点与终点,则总的连接为m-(n-1)+(n-1)=m条证毕

当n>m也同理可证

#include<bits/stdc++.h>
using namespace std;
const int N=110,M=N*N;
int n;
int h[N],e[M],ne[M],idx;
int dfn[N],low[N],timestamp;
int stk[N],top;
bool in_stk[N];
int id[N],scc_cnt;
int din[N],dout[N];
void add(int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void tarjan(int u)
{
    low[u]=dfn[u]=++timestamp;//获取这个位置的时间戳
    stk[++top]=u,in_stk[u]=true;//标记这个点在栈里了
    for(int i=h[u];~i;i=ne[i])
    {
        int j=e[i];
        if(!dfn[j])//假如没有更新过
        {
            tarjan(j);
            low[u]=min(low[u],low[j]);//更新一遍最小值
        }
        else if(in_stk[j]) low[u]=min(low[u],dfn[j]);//假如已经在栈里了,也更新最小值
    }
    if(low[u]==dfn[u])//假如搜索到头了
    {
        ++scc_cnt;//则最大强连通数量++
        int y;
        do
        {
            y=stk[top--];//取出栈顶
            in_stk[y]=false;//标记不在栈中了
            id[y]=scc_cnt;//让这个点归到强这个强连通分支中
        }while(y!=u);
    }
}
int main()
{
    memset(h,-1,sizeof h);
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        int t;
        while(cin>>t,t)
        {
            add(i,t);
        }
    }
    for(int i=1;i<=n;i++)
        if(!dfn[i])//假如还没搜索过
            tarjan(i);
   for(int u=1;u<=n;u++)//枚举缩点后的
     for(int i=h[u];~i;i=ne[i])
     {
         int j=e[i];
         int a=id[u],b=id[j];//获取两个分别所在的强连通分量中
         if(a!=b) dout[a]++,din[b]++;//让a出度++,b入度++
     }
  int in=0,out=0;
  for(int i=1;i<=scc_cnt;i++)//分别获取起点与终点,也就是入度为0的点与出度为0的点
  {
      if(!din[i]) in++;//入度为0是起点
      if(!dout[i]) out++;//出度为0是终点
  }
  cout<<in<<endl;//输出起点的数就是需要分发的个数
  if(scc_cnt==1) puts("0");//假如只有一个强连通分支说明没有终点
  else cout<<max(in,out)<<endl;//否则输出二者最大的数
    return 0;
}

3.最大半连通子图

信息学奥赛一本通(C++版)在线评测系统 (ssoier.cn)

1.tarjan

2.缩点判重

3.按照拓扑序递推

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10,M=2e6+10;
int h[N],hs[N],e[M],ne[M],idx;//h是原来的图,hs是缩点后建的图
int dfn[N],low[N],timestamp;
int stk[N],top;
bool in_stk[N];
int id[N],scc_cnt,size[N];//size记录每个最大连通分量的数量
int f[N],g[N];//f是点的个数,g是方案数
int n,m,mod;
unordered_set<ll> ha;//用来判重
void add(int h[],int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void tarjan(int u)//用tarjan搜索强连通分量
{
    dfn[u]=low[u]=++timestamp;
    stk[++top]=u,in_stk[u]=true;
    for(int i=h[u];~i;i=ne[i])
    {
        int j=e[i];
        if(!dfn[j])
        {
            tarjan(j);
            low[u]=min(low[u],low[j]);
        }
        else if(in_stk[j]) low[u]=min(low[u],dfn[j]);
    }
    if(dfn[u]==low[u])
    {
        ++scc_cnt;
        int y;
        do
        {
            y=stk[top--];
            in_stk[y]=false;
            size[scc_cnt]++;//该强连通分量个数加1
            id[y]=scc_cnt;
        }while(y!=u);
    }
}
int main()
{
    memset(h,-1,sizeof h);
    memset(hs,-1,sizeof hs);
    scanf("%d%d%d",&n,&m,&mod);
    while(m--)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        add(h,a,b);
    }
    for(int i=1;i<=n;i++)//跑一遍tarjan合并强连通分量
        if(!dfn[i])
          tarjan(i);
    for(int u=1;u<=n;u++)//建缩了点后的图
        for(int i=h[u];~i;i=ne[i])
        {
           int j=e[i];
           int a=id[u],b=id[j];
           ll temp=a*1000000ll+b;
           if(a!=b&&!ha.count(temp))//假如不在一个连通分量里和避免重复建边
           {
               add(hs,a,b);
               ha.insert(temp);
           }
        }
    //跑完tarjan得到的强连通分量就是一个拓扑序
    for(int u=scc_cnt;u;u--)//从大到小就是拓扑序
    {
        if(!f[u]) f[u]=size[u],g[u]=1;//假如这个点没有点,则让这个f等于该连通分量的数,方案就自己一个
        for(int i=hs[u];~i;i=ne[i])//枚举所有与他相连的强连通分量
        {
            int j=e[i];
            if(f[u]+size[j]>f[j])//假如其他点的个数比我大,则更新
            {
                f[j]=f[u]+size[j];//更新最大值
                g[j]=g[u];//方案数等于他的方案数
            }
            else if(f[u]+size[j]==f[j]) g[j]=(g[j]+g[u])%mod;//假如相等,则加上这个方案数
        }
    }
    int maxf=0,sum=0;
    for(int i=1;i<=scc_cnt;i++)//求最多的点的方案数
        if(f[i]>maxf) maxf=f[i],sum=g[i];
        else if(f[i]==maxf) sum=(sum+g[i])%mod;
    printf("%d\n%d\n",maxf,sum);//输出最多的点和其方案数
    return 0;
}

4.银河

368. 银河 - AcWing题库

这题跟糖果那一题一样,但是用差分约束解决的spfa会被卡掉,所以用最大连通分量保证o(n+m)不会被卡

1.tarjan

2.缩点

3.依据拓扑序递推

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10,M=6e6+10;
int n,m;
int h[N],hs[N],e[M],ne[M],w[M],idx;
int dfn[N],low[N],timestamp;
int stk[N],top;
bool in_stk[N];
int id[N],scc_cnt,sizes[N];
int dist[N];
void add(int h[],int a,int b,int c)
{
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
void tarjan(int u)//常规的tarjan模板
{
    dfn[u]=low[u]=++timestamp;
    stk[++top]=u,in_stk[u]=true;
    for(int i=h[u];~i;i=ne[i])
    {
        int j=e[i];
        if(!dfn[j])
        {
            tarjan(j);
            low[u]=min(low[u],low[j]);
        }
        else if(in_stk[j]) low[u]=min(low[u],dfn[j]);
    }
    if(low[u]==dfn[u])
    {
        ++scc_cnt;
        int y;
        do
        {
            y=stk[top--];
            in_stk[y]=false;
            id[y]=scc_cnt;
            sizes[scc_cnt]++;
        }while(y!=u);
    }
}
int main()
{
    memset(h,-1,sizeof h);
    memset(hs,-1,sizeof hs);
   scanf("%d%d",&n,&m);
   for(int i=1;i<=n;i++) add(h,0,i,1);//保证所有数都是大于1的
   while(m--)
   {
       int t,a,b;
       scanf("%d%d%d",&t,&a,&b);
       if(t==1) add(h,a,b,0),add(h,b,a,0);
       else if(t==2) add(h,a,b,1);
       else if(t==3) add(h,b,a,0);
       else if(t==4) add(h,b,a,1);
       else add(h,a,b,0);
   }
   tarjan(0);//因为0号点跟所有点都连了边所以不用枚举其他点了
   bool f=true;//判断是否有环
  for(int u=0;u<=n;u++)
  {
    for(int i=h[u];~i;i=ne[i])
      {
        int j=e[i];
        int a=id[u],b=id[j];
        if(a==b)//一个环中,也就是一个连通分量中
        {
            if(w[i]>0)//最大连通量里的边权大于1,说明有正环
            {
                f=false;
                break;
            }
        }
        else add(hs,a,b,w[i]);//反之其他连通分量建边就是拓扑序
      }
      if(!f) break;
  }
  if(!f) puts("-1");
  else
  {
      for(int u=scc_cnt;u;u--)//按照从大到小就是拓扑序
        for(int i=hs[u];~i;i=ne[i])
        {
            int j=e[i];
             dist[j]=max(dist[j],dist[u]+w[i]);//更新每个点的距离最大值
        }
     ll res=0;
     for(int i=1;i<=scc_cnt;i++) res+=(ll)dist[i]*sizes[i];//最大距离就是该点的距离乘以点数
     printf("%lld\n",res);
  }
    return 0;
}
相关文章
|
11月前
|
安全 算法 Go
有向图的强联通分量(SCC)Tarjan算法
有向图的强联通分量(SCC)Tarjan算法
132 0
|
22天前
|
存储 机器学习/深度学习 并行计算
阿里云服务器X86计算、Arm计算、GPU/FPGA/ASIC、高性能计算架构区别
在我们选购阿里云服务器的时候,云服务器架构有X86计算、ARM计算、GPU/FPGA/ASIC、弹性裸金属服务器、高性能计算可选,有的用户并不清楚他们之间有何区别,本文主要简单介绍下不同类型的云服务器有何不同,主要特点及适用场景有哪些。
阿里云服务器X86计算、Arm计算、GPU/FPGA/ASIC、高性能计算架构区别
|
11月前
|
存储 弹性计算 运维
阿里云弹性裸金属服务器_弹性物理机_高性能计算服务_弹性计算
阿里云弹性裸金属服务器_弹性物理机_高性能计算服务_弹性计算,阿里云弹性裸金属服务器(ECS Bare Metal Server)是一种可弹性伸缩的高性能计算服务,原神龙服务器,计算性能与传统物理机无差别,具有安全物理隔离的特点,裸金属服务器分钟级的交付周期
107 0
|
机器学习/深度学习 人工智能 并行计算
带你读《生命科学行业云上解决方案及最佳实践》——GHDDI,阿里云高性能计算助力 药物研发实现高通量分子筛选
带你读《生命科学行业云上解决方案及最佳实践》——GHDDI,阿里云高性能计算助力 药物研发实现高通量分子筛选
180 0
|
弹性计算 云计算
阿里云产品体系分为6大分类——云计算基础——弹性计算——高性能计算HPC
阿里云产品体系分为6大分类——云计算基础——弹性计算——高性能计算HPC自制脑图
162 1
阿里云产品体系分为6大分类——云计算基础——弹性计算——高性能计算HPC
|
存储 人工智能 弹性计算
阿里云高性能计算负责人何万青:阿里云大计算加速HPC与AI融合
与AI相结合,高性能计算能够帮助科研人员将精力集中于专业领域。
阿里云高性能计算负责人何万青:阿里云大计算加速HPC与AI融合
|
数据采集 弹性计算 编解码
阿里云架构师马颂:云上高性能计算助力基因测序
基于E-HPC的大内存实例解决方案:算得快、成本低、简运维、助生态
阿里云架构师马颂:云上高性能计算助力基因测序
|
人工智能 弹性计算 运维
阿里云架构师朱波:云上高性能计算加速药物研发
资源的弹性供应能力、灵活的定价模式以及高效的运维管理。
阿里云架构师朱波:云上高性能计算加速药物研发

热门文章

最新文章