有向图的强连通分量 SCC

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

强连通分量指的是极大连通分量

应用:主要作用是可以将一个有向图转换为有向无环图(DAG)(拓扑图) 通过缩点的形式 将所有连通的分量缩成一个点

有向无环图有什么好处:求最短路或者最长路的时候可以直接递推   O(n+m)

一般怎么求强连通分量:dfs

将边分为四大类

(1).树枝边 x y 比如x是y的父亲那么(x,y)就是树枝边

(2).前向边 x y 比如x是y的祖先节点就是前向边

(3).后向边 和前向边相反

(4).横叉边 往已经搜过的其他分支搜的边

如果判断某个点在不在强连通分量中

1.存在后向边指向祖先节点

2.先走到横叉边 然后横叉边再往上走到祖先节点

 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.受欢迎的牛

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

1.tarjan

2.缩点

3.枚举求答案

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

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

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

#include <bits/stdc++.h>
using namespace std;
const int N=10010,M=50010;
int n,m;
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];
int scc_cnt,size[N];
int dout[N];
void add(int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
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])
    {
        ++scc_cnt;
        int y;
        do{
            y=stk[top--];
            in_stk[y]=false;
            id[y]=scc_cnt;
            size[scc_cnt]++;
        }while(y!=u);
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    memset(h,-1,sizeof h);
    while(m--)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        add(a,b);
    }
    for(int i=1;i<=n;i++)
        if(!dfn[i])
            tarjan(i);
    for(int i=1;i<=n;i++)
    {
        for(int j=h[i];~j;j=ne[j])
        {
            int k=e[j];
            int a=id[i],b=id[k];
            if(a!=b)
                dout[a]++;
        }
    }
    int zeros=0,sum=0;
    for(int i=1;i<=scc_cnt;i++)
        if(!dout[i])
        {
            zeros++;
            sum+=size[i];
            if(zeros>1)
            {
                sum=0;
                break;
            }
        }
    printf("%d\n",sum);
    return 0;
}

2.学校网络

367. 学校网络 - AcWing题库

1.tarjan

2.缩点

3.求入度与出度 

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

最少给的软件就是给起点

第二问结论为max( 起点 ,终点 )

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

设起点为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=10010;
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 din[N],dout[N];
int id[N],scc_cnt;
void add(int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
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])
    {
        ++scc_cnt;
        int y;
        do{
            y=stk[top--];
            in_stk[y]=false;
            id[y]=scc_cnt;
        }while(y!=u);
    }
}
int main()
{
    scanf("%d",&n);
    memset(h,-1,sizeof h);
    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 i=1;i<=n;i++)
    {
        for(int j=h[i];~j;j=ne[j])
        {
            int k=e[j];
            int a=id[i],b=id[k];
            if(a!=b)
            {
                dout[a]++;
                din[b]++;
            }
        }
    }
    int a=0,b=0;
    for(int i=1;i<=scc_cnt;i++)
    {
        if(!din[i])
            a++;
        if(!dout[i])
            b++;
    }
    printf("%d\n",a);
    if(scc_cnt==1) puts("0");
    else printf("%d\n",max(a,b));
    return 0;
}

3.最大半连通子图

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

1.tarjan

2.缩点判重

3.按照拓扑序递推

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=100010,M=2000010;
int n,m,mod;
int h[N],hs[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 size[N];
int f[N],g[N];
void add(int h[],int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
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])
    {
        ++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);
    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++)
        if(!dfn[i])
            tarjan(i);
    unordered_set<ll> s;
    for(int i=1;i<=n;i++)
        for(int j=h[i];~j;j=ne[j])
        {
            int k=e[j];
            int a=id[i],b=id[k];
            ll hash=a*1000000ll+b;
            if(a!=b&&!s.count(hash))
            {
                add(hs,a,b);
                s.insert(hash);
            }
        }
    for(int i=scc_cnt;i;i--)
    {
        if(!f[i])
        {
            f[i]=size[i];
            g[i]=1;
        }
        for(int j=hs[i];~j;j=ne[j])
        {
            int k=e[j];
            if(f[k]<f[i]+size[k])
            {
                f[k]=f[i]+size[k];
                g[k]=g[i];
            }
            else if(f[k]==f[i]+size[k])
                g[k]=(g[k]+g[i])%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",maxf);
    printf("%d\n",sum);
    return 0;
}

4.银河

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

 首先判定无解的情况,因为每一个节点的权值都必定是大于0的,所以无解的情况就是存在正环

#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;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值