bzoj [HAOI2006]受欢迎的牛 (强联通分量+缩点)

本文介绍使用Tarjan算法求解有向图的强联通分量,并通过实例讲解如何实现该算法。通过枚举每条边并利用栈结构,可以有效地找到所有强联通分量,最终解决特定图论问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

强联通分量参考神犇博客:

https://www.byvoid.com/zhs/blog/scc-tarjan

https://www.luogu.org/blog/styx-ferryman/chu-tan-tarjan-suan-fa-qiu-qiang-lian-tong-fen-liang-post

题目链接:传送门

题解:直接弄个强联通分量,然后枚举每条边,假设x ,y不在同一个强联通块,那么就让强联通块x出度加1,最后找到出度为0的强联通块就行了,但只能有一个,要是超过了一个,就不符合题意了。

 

代码如下:

#include<cstdio>
#include<algorithm>
#include<cstring>

using namespace std;

const int maxn=50010;

int x[maxn],y[maxn];
int DFN[maxn],LOW[maxn],next[maxn],point[maxn],v[maxn],vis[maxn];
int belong[maxn],top,tot,cnt,sizes[maxn],mark[maxn],stacks[maxn],index;

void add(int x,int y){
    tot++;
    next[tot]=point[x];
    point[x]=tot;
    v[tot]=y;
}

void tarjan(int x) ///强联通模板
{
    stacks[++top]=x; ///此点进栈
    vis[x]=1;  ///标记是否已经访问过

    DFN[x]=LOW[x]=++index; ///给编号

    for(int i=point[x];i;i=next[i])
    {
        int j=v[i];
        if(!DFN[j]){
            tarjan(j);
            LOW[x]=min(LOW[x],LOW[j]);
        }
        else if(vis[j]) LOW[x]=min(LOW[x],LOW[j]);
    }

    if(LOW[x]==DFN[x])
    {
        cnt++; ///有向图强联通分量有多少块
        int item;
        do{
             item=stacks[top--];
            vis[item]=0;
            belong[item]=cnt; ///表示点item属于cnt强联通块
            sizes[cnt]++; ///表示cnt块有多少个点
        }while(item!=x);
    }
}

int main()
{

    int n,m;
    while(~scanf("%d%d",&n,&m))
    {

        for(int i=1;i<=m;i++){
            scanf("%d%d",&x[i],&y[i]);
            add(x[i],y[i]);
        }

        memset(DFN,0,sizeof(DFN)); ///初始化
        memset(LOW,0,sizeof(LOW));
        memset(vis,0,sizeof(vis));
        memset(vis,0,sizeof(vis));

        top=tot=index=0;
        for(int i=1;i<=n;i++)
        {
            if(!DFN[i]) tarjan(i);
        }

        memset(mark,0,sizeof(mark));
        for(int i=1;i<=m;i++){

            if(belong[x[i]]!=belong[y[i]]){ ///表示有两块
                mark[belong[x[i]]]++; ///在第一块初度加1
            }
        }

        int total=0,flag=true;

        for(int i=1;i<=cnt;i++)
        {
            if(!mark[i]){
                if(total){ ///表示至少有两块出度为0的块,输出0
                    flag=false;break;
                }
                total=i;
            }

        }
//        printf("total=%d\n",total);

        if(flag) printf("%d\n",sizes[total]); ///只要一块出度为0的块
        else printf("0\n");
    }

    return 0;
}

 

我的标签:做个有情怀的程序员。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值