【POJ 2186】 Popular Cows Tarjan 缩点 详解

22 篇文章 0 订阅
17 篇文章 0 订阅

Description

Every cow’s dream is to become the most popular cow in the herd. In a herd of N (1 <= N <= 10,000) cows, you are given up to M (1 <= M <= 50,000) ordered pairs of the form (A, B) that tell you that cow A thinks that cow B is popular. Since popularity is transitive, if A thinks B is popular and B thinks C is popular, then A will also think that C is
popular, even if this is not explicitly specified by an ordered pair in the input. Your task is to compute the number of cows that are considered popular by every other cow.
Input

  • Line 1: Two space-separated integers, N and M

  • Lines 2…1+M: Two space-separated numbers A and B, meaning that A thinks B is popular.
    Output

  • Line 1: A single integer that is the number of cows who are considered popular by every other cow.
    Sample Input

3 3
1 2
2 1
2 3
Sample Output

1
Hint

Cow 3 is the only cow of high popularity.

题意:牛与牛之间有崇拜关系。问有多少牛被所有牛崇拜

思路:

分析满足条件的状态:如果最后全部只有一只牛被所有牛崇拜,那么也就是这头牛没有出度(崇拜者)。如果最后有多只牛被所有牛崇拜,那么要满足题目所说“同时被其他所有牛崇拜”,这些牛内部之间两两要构成崇拜关系,不然就不能说任意一头牛都有n-1个崇拜者。 换言之,就是答案的这k头牛之间要形成一个强联通分量(k=1时仍满足)。
那么,我们就可以将图看成若干强联通分量组成的图。如图:
在这里插入图片描述
因为题目所说崇拜具有传递性,所以这幅图我们可以看成从下到上(或者说从上到下)崇拜的传递。橙色所代表的即是最后答案的被所有牛崇拜的三头牛。
什么时候会有不满足题意的图产生呢?看下图:
在这里插入图片描述
我们发现两个橙色联通分量之间没有构成联系。所以比不可能有牛同时被所有牛崇拜。所以,当且仅当出度为0的联通分量个数只有一个时,有解。这时,我们只需要把每个联通分量看成一个点,然后计算出度即可。 借助Tarjan进行连通分量染色,再对其缩点,记录出度。

图一的缩点:

在这里插入图片描述

详细过程见注释

AC代码:

#include<iostream>
#include<string>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<map>
#include <queue>
#include<sstream>
#include <stack>
#include <set>
#include <bitset>
#include<vector>
#define FAST ios::sync_with_stdio(false)
#define abs(a) ((a)>=0?(a):-(a))
#define sz(x) ((int)(x).size())
#define all(x) (x).begin(),(x).end()
#define mem(a,b) memset(a,b,sizeof(a))
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define rep(i,a,n) for(int i=a;i<=n;++i)
#define per(i,n,a) for(int i=n;i>=a;--i)
#define pb push_back
#define mp make_pair
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef pair<ll,ll> PII;
const int maxn = 1e4+500;
const int inf=0x3f3f3f3f;
const double eps = 1e-7;
const double pi=acos(-1.0);
const int mod = 1e9+7;
inline int lowbit(int x){return x&(-x);}
ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
void ex_gcd(ll a,ll b,ll &d,ll &x,ll &y){if(!b){d=a,x=1,y=0;}else{ex_gcd(b,a%b,d,y,x);y-=x*(a/b);}}//x=(x%(b/d)+(b/d))%(b/d);
inline ll qpow(ll a,ll b,ll MOD=mod){ll res=1;a%=MOD;while(b>0){if(b&1)res=res*a%MOD;a=a*a%MOD;b>>=1;}return res;}
inline ll inv(ll x,ll p){return qpow(x,p-2,p);}
inline ll Jos(ll n,ll k,ll s=1){ll res=0;rep(i,1,n+1) res=(res+k)%i;return (res+s)%n;}
inline ll read(){ ll f = 1; ll x = 0;char ch = getchar();while(ch>'9'||ch<'0') {if(ch=='-') f=-1; ch = getchar();}while(ch>='0'&&ch<='9') x = (x<<3) + (x<<1) + ch - '0',  ch = getchar();return x*f; }
int dir[4][2] = { {1,0}, {-1,0},{0,1},{0,-1} };

vector<vector<ll> > D(maxn);
ll dfn[maxn], low[maxn], vis[maxn], s[maxn], cnt[maxn], sum=0, idx = 0 , color[maxn];
ll up = 0;
ll n,m;
ll du[maxn];

void init()     //初始化
{
    mem(dfn,0);mem(low,0);mem(vis,0);mem(s,0);
    mem(cnt,0);mem(color,0);mem(du,0);
    for(int i=1;i<=n;i++) D[i].clear();
    sum = idx = up = 0;
}

void Tarjan(ll u)       //Tarjan算法
{
    dfn[u] = low[u] = ++idx;        //时序
    vis[u] = 1; s[++up] = u;        //进栈并标记
    for(int i=0;i<D[u].size();i++)      //找子树
    {
        int v = D[u][i];        
        if(!dfn[v])     //如果没有遍历过
        {
            Tarjan(v);      //往下搜索
            low[u] = min(low[u],low[v]);  //看看这个点下去能不能祖先,回得到的话low[v] < low[u]
        }
        else        //若已经遍历过
        {
            //而且在栈中,同样,这整个为一个强联通分量
            if(vis[v]) low[u] = min(low[u],low[v]);
        }
    }
    if(dfn[u]==low[u])  //如果往下没有回到祖先的
    {
        color[u] = ++sum;       //那么这一整块(可能是强联通分量的根或者单一点)染色
        vis[u] = 0;     //相当于出栈
        while(s[up] != u)       //u之前的点出栈,这些都是一个强联通分量
        {
            color[s[up]] = sum;     //染上相同颜色
            vis[s[up--]] = 0;       //清除标记
        }
        up--;       //别忘了当前这个点,去掉了后up--
    }
}

int main()
{
    while(~scanf("%lld%lld",&n,&m))
    {
        init();
        rep(i,1,m)      //读入
        {
            ll x,y; x = read(); y = read();
            D[x].pb(y);
        }
        rep(i,1,n) if(dfn[i]==0) Tarjan(i);     //因为有可能整个图不连通,对每个没遍历过的试探
        
        rep(i,1,n)      //缩点操作
        {
            for(int j=0;j<D[i].size();j++)
            {
                ll v = D[i][j];             
                if(color[v]!=color[i]) du[color[i]] ++;         //如果它这个点和子树不在同一个强联通分量内,就将i所属联通分量看作一个点(缩点)。连向v所属联通分量。出度++
            }
            cnt[color[i]] ++;   //记录这个强联通分量含有的点的个数
        }
        
        ll obj = 0;ll ans=0;
        rep(i,1,sum)
        if(du[i]==0) obj++, ans=cnt[i];     //看看出度为0的点有多少个
        
        if(obj==0||obj>1) cout<<0<<'\n';        //0个不用说,答案为0 。 如果答案大于1, 那么这些出度为0的缩点内的牛互相不崇拜,结果肯定是0。
        else cout<<ans<<'\n';       //否则输出答案
    }
    return 0;
}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值