BZOJ1051: [HAOI2006]受欢迎的牛

Description
每一头牛的愿望就是变成一头最受欢迎的牛。现在有N头牛,给你M对整数(A,B),表示牛A认为牛B受欢迎。
这种关系是具有传递性的,如果A认为B受欢迎,B认为C受欢迎,那么牛A也认为牛C受欢迎。你的任务是求出有多少头牛被所有的牛认为是受欢迎的。
Input
第一行两个数N,M。 接下来M行,每行两个数A,B,意思是A认为B是受欢迎的(给出的信息有可能重复,即有可能出现多个A,B)
Output
一个数,即有多少头牛被所有的牛认为是受欢迎的。
Sample Input
3 3
1 2
2 1
2 3
Sample Output
1
HINT
100%的数据N<=10000,M<=50000
Source

首先建图,根据关系来连边(A认为B受欢迎=A→B)。“必须被所有的牛欢迎”这个条件相当苛刻,那么什么情况下最终的答案会>1?这种情况只会出现在一个强连通分量中(互相觉得对方受欢迎)(当然一个单独结点也可视为一个强连通分量),这个强连通分量中每个节点都是答案,仔细想一想就会发现最终构成答案的强连通分量只会有一个,而且强连通分量中如果有一头牛认为某一头牛受欢迎,那么就可以得到这个强连通分量中所有牛就都认为这头牛受欢迎。利用这个性质,我们就可以依靠强连通分量的算法。
注意:next在某些版本编译器中为关键字,可能导致CE,使用next数组务必小心
方法一:Tarjan后进行DFS并统计次数,若某个强连通分量能被其它所有强连通分量访问到,那它就是答案。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
const int N=10010;
const int M=50010;
int n,m,co,index1,num,next1[M],point[N],to[M];
int dfn[N],low[N],t[N],stack[N],top,ans[N],ans1;//i结点所在的强连通分量为t[i],ans[i]统计第i个强连通分量的结点数
bool vis[N],vis1[N],vis2[N],instack[N];int ans2[N];
void in(int &x)
{
    char t=getchar();int f=1;x=0;
    while((t<48)or(t>57)){if(t=='-')f=-1;t=getchar();}
    while((t>=48)and(t<=57)){x=x*10+t-48;t=getchar();}
    x*=f;
}
void add(int from,int to1)
{
    ++co;
    next1[co]=point[from];
    point[from]=co;
    to[co]=to1;
}
void tarjan(int u)
{
    vis[u]=instack[u]=1;
    stack[++top]=u;
    low[u]=dfn[u]=++index1;
    for (int now=point[u];now;now=next1[now])
    {
        int v=to[now];
        if (!vis[v])
        {
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else
        if (instack[v]) low[u]=min(low[u],dfn[v]);
    }
    if (dfn[u]==low[u])
    {
        ++num;int v=0;
        while(v!=u)
        {
            v=stack[top--];
            t[v]=num;++ans[num];
            instack[v]=0;
        }
    }
}
void dfs(int now,int now1)
{
    vis[now]=1;
    if ((t[now]!=now1)and(!vis1[t[now]]))
    {++ans2[t[now]];vis1[t[now]]=1;}
    for (int i=point[now];i;i=next1[i])
    {
        int v=to[i];
        if (!vis[v]) dfs(v,now1);
    }
}
int main()
{
    in(n),in(m);
    for (int i=1;i<=m;++i)
    {
        int x,y;
        in(x),in(y);
        add(x,y);
    }
    for (int i=1;i<=n;++i)
    if (!vis[i]) tarjan(i);
    for (int i=1;i<=n;++i)
    if (!vis2[t[i]])
    {
        memset(vis,0,sizeof(vis));
        memset(vis1,0,sizeof(vis1));
        dfs(i,t[i]);vis2[t[i]]=1;
    }
    for (int i=1;i<=n;++i)
    if (ans2[t[i]]==(num-1))
    {ans1=ans[t[i]];break;}
    printf("%d",ans1);
    return 0;
}

方法二:利用强连通分量来缩点,把整个强连通分量视为一个结点并重新建图连边,出度为0者即为答案。
注意:可能出现某一个强连通分量被孤立的情况(入度出度都为0),解决方法是把所有强连通分量都扫一遍,如果出现多个出度为0的强连通分量则输出0。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
const int N=10010;
const int M=50010;
int n,m,co,co1,index1,num,next1[M],point[N],to[M];
int dfn[N],low[N],t[N],stack[N],top,ans[N],ans1;
bool vis[N],instack[N];int point2[N],next2[M],to2[M];
void in(int &x)
{
    char t=getchar();int f=1;x=0;
    while((t<48)or(t>57)){if(t=='-')f=-1;t=getchar();}
    while((t>=48)and(t<=57)){x=x*10+t-48;t=getchar();}
    x*=f;
}
void add(int from,int to1)
{
    ++co;
    next1[co]=point[from];
    point[from]=co;
    to[co]=to1;
}
void add1(int from,int to1)
{
    ++co1;
    next2[co1]=point2[from];
    point2[from]=co1;
    to2[co1]=to1;
}
void tarjan(int u)
{
    vis[u]=instack[u]=1;
    stack[++top]=u;
    low[u]=dfn[u]=++index1;
    for (int now=point[u];now;now=next1[now])
    {
        int v=to[now];
        if (!vis[v])
        {
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else
        if (instack[v]) low[u]=min(low[u],dfn[v]);
    }
    if (dfn[u]==low[u])
    {
        ++num;int v=0;
        while(v!=u)
        {
            v=stack[top--];
            t[v]=num;++ans[num];
            instack[v]=0;
        }
    }
}
void work(int u)
{
    for (int i=point[u];i;i=next1[i])
    {
        int v=to[i];
        if (t[v]!=t[u]) add1(t[u],t[v]);
    }
}
int main()
{
    in(n),in(m);
    for (int i=1;i<=m;++i)
    {
        int x,y;
        in(x),in(y);
        add(x,y);
    }
    for (int i=1;i<=n;++i)
    if (!vis[i]) tarjan(i);
    for (int i=1;i<=n;++i) work(i);
    for (int i=1;i<=num;++i)
    if (!point2[i])
        if (ans1) {ans1=0;break;}
        else ans1=ans[i];
    printf("%d",ans1);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值