【POJ 2186】 Popular Cows Tarjan 縮點 詳解

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章