有向图的强连通分量——学校网络

二次元入口:367. 学校网络 - AcWing题库

思路:

根据题目建好有向图后走一遍tarjan算法获得强连通分量,记录每一个点的入度和出度,一个点在得到的强连通分量图里面是起点的话入度会是0,是终点的话出度会是0.

对于第一个问题,需要至少直接提供给多少个学校,就是提供给拓扑图中的所有起点的强连通分量,起点的数量就是第一问的答案。

第二问答案的结论是设拓扑图里面的起点数量为a,终点的数量为b,答案是max(a,b).

第一问的证明很好想,第二问证明?我也还没完全看懂。

证明:

假设起点的数量p,终点的数量是q(以下的连边操作都是在缩完点后的拓扑图上面进行)

特判:当强连通分量只有一个时,答案为0.

在p<=q时

1.当p==1,需要从q个终点都连一条边连向起点,这样才能使得从任意一个强连通分量出发都可以走到任意一个节点。(比较好想)

2.当p>1时,有q>=p>=1,这时候,必定会有两个起点p1,p2,和两个终点q1,q2

使得p1能够走到q1 , p2能够走到q2,从q1连一条边到p2,  p和q同时减1,相当于同时减少了一个起点和一个终点。

按照这个操作进行p-1次,最后变成情形1,此时已经连的边的数量就是p-1,还需要连的边的数量就是q-(p-1),加起来就是q条

代码:

#include<iostream>
#include<cstring>
#include<algorithm>
#include<stack>
using namespace std;
typedef long long ll;
const int N=110,M=1e4+10;
int e[M],ne[M],h[N],idx;
int dfn[N],times,low[N];
int stk[N],top;
bool in_stk[N];
int id[N],scc_cnt;
int din[N],dout[N];
int 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]=++times;

    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()
{
    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;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值