受欢迎的牛bzoj1051(浅谈tarjan——(3))

今天发的第三篇有关tarjan算法的文章了。
tarjan这个算法也是我刚学的,昨天做了APIO抢掠计划。一道tarjan+最长路。
硬是懵了一个下午,所以决定学tarjan。(有兴趣的同学可以去打一下)

好了,言归正传,这次的题目要靠tarjan缩点了(缩点是tarjan一个非常重要的用途)

题目描述

每头奶牛都梦想成为牛棚里的明星。被所有奶牛喜欢的奶牛就是一头明星奶牛。所有奶

牛都是自恋狂,每头奶牛总是喜欢自己的。奶牛之间的“喜欢”是可以传递的——如果A喜

欢B,B喜欢C,那么A也喜欢C。牛栏里共有N 头奶牛,给定一些奶牛之间的爱慕关系,请你

算出有多少头奶牛可以当明星。
输入输出格式
输入格式:

第一行:两个用空格分开的整数:N和M

第二行到第M + 1行:每行两个用空格分开的整数:A和B,表示A喜欢B

输出格式:

第一行:单独一个整数,表示明星奶牛的数量

输入输出样例
输入样例#1:

3 3
1 2
2 1
2 3

输出样例#1:

1

说明

只有 3 号奶牛可以做明星

【数据范围】

10%的数据N<=20, M<=50

30%的数据N<=1000,M<=20000

70%的数据N<=5000,M<=50000

100%的数据N<=10000,M<=50000


我们可以先得出一个推论:在DAG(有向无环图)中,如果有且仅有一个点,它的出度为0,那么所有点都可以到这个点。
这里写图片描述
如图,图中有且仅有粉红色的点出度为0,而所有点都经过这个点。
这里写图片描述
而这张图中,黄色和粉红色都出度为0,所以此图没有任何一个点被所有点经过。

通过这个推论,我们可以将一张有向有环图用tarjan缩点的办法缩成一张有向无环图,
这样只要求出每个点的出度即可。
我们可以看此题样例:
这里写图片描述
粉色的点与红色的点构成一个环(强连通分量),所以可以用tarjan算法
将其缩成蓝色的点

此题的大致思路就是这样,代码细节较多,在程序里解释。

#include<bits/stdc++.h>
#include<vector>

using namespace std;

int i,j,k,n,m,tot,ans,top,co_num,ta_num,ansrecord,totrecord;
vector<int> f[10005];
int dfn[10005],low[10005],vis[10005],sta[10005],re[10005];
struct node{
    int num;
    vector<int> go,team;
}a[10005];

int read(){
    char c;int x;while(c=getchar(),c<'0'||c>'9');x=c-'0';
    while(c=getchar(),c>='0'&&c<='9') x=x*10+c-'0';return x;
}

void tarjan(int x){
    ta_num++;
    dfn[x]=low[x]=ta_num;
    vis[x]=1;sta[++top]=x;
    for(int i=0;i<f[x].size();i++){
        int temp=f[x][i];
        if(!dfn[temp]){
            tarjan(temp);low[x]=min(low[x],low[temp]);
        }
        else if(vis[temp]) low[x]=min(low[x],dfn[temp]);
    }
    if(dfn[x]==low[x]){
        int now=0;
        tot++;
        while(now!=x){
            now=sta[top];
            top--;
            a[tot].num++; 
            a[tot].team.push_back(now);   //缩点,将一个强连通分量里的信息缩到a[tot]
            vis[now]=0;
        }
        memset(re,0,sizeof(re));
        for(int i=0;i<a[tot].team.size();i++)
            re[a[tot].team[i]]=1;
        for(int i=0;i<a[tot].team.size();i++){
            int next=a[tot].team[i];
            for(int j=0;j<f[next].size();j++)
              if(!re[f[next][j]]) a[tot].go.push_back(f[next][j]);
        }   //这部将强连通分量所有点所走出的边缩到一起,注意将连接同一强连通分量的边去除
    }
}

int main()
{
    n=read();m=read();
    for(int i=1;i<=m;i++){
        int x=read(),y=read();
        f[x].push_back(y);
    }
    for(int i=1;i<=n;i++)
       if(!dfn[i]) tarjan(i);   
    for(int i=1;i<=tot;i++)
       if(a[i].go.empty()){  //找出度为0的点
            totrecord++;
            ansrecord=a[i].num;
       }
    if(totrecord==1) printf("%d",ansrecord);//判断是否只有一个点出度为0
    else printf("0");
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值