usaco 月赛 2003 Fall Popular Cows 受欢迎的奶牛 题解

47 篇文章 2 订阅
24 篇文章 0 订阅

Popular Cows题解
Time Limit: 2000MS Memory Limit: 65536K
Total Submissions: 21696 Accepted: 8859

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. 

Source


【大意】给定N(N<=10000)个点和M(M<=50000)条边(注意:是有向边),求有多少个“受欢迎的点”。所谓的“受欢迎的点”当且仅当任何一个点出发都能到达它。

【首先发现】99%的人都会想到直接用floyed来求。可惜的是,N太大了。我们再考虑新的算法。

【分析】让人厌烦的是,这道题可能会有环,即A--B,B--C,C--A。

先考虑无环的情况。定理1:若有向无环图是连通的,只有出度为0的点才是“受欢迎的点”。

伪证明:设某点X是“受欢迎的点”,且该点仍有出度。不妨设它能到达Y点。因为是有向无环图,那么Y是一定不能到达X点了!即X点并不是“受欢迎的点”——与条件矛盾,命题得证。

定理2:若有向无环图是连通的,当存在大于1个出度为0的点,则图中没有“受欢迎的点”。

伪证明设点X和点Y是出度为0的点,那么X不能到达Y。命题得证。

【小结】太容易了吧!我们只要寻找出度为0的点,如果不止一个,就没有答案,否则就是那个点!

【深入思考】下面我们的任务就是要缩环成点。著名的Tarjan算法跳出来了!它的效率是O(N+M),太厉害了!(如果不知道Tarjan的实现过程,点击这里)那么只要在找到每一个强连通分量之后,对它中的每一个点并查集维护一下,就能缩成一个点了。

【注意点】①数量大,要用边表来记录。

调用Tarjan算法时,要循环每一个点。一开始我一直WA,就是因为只从1号点进入递归。

【代码】

#include<stdio.h>
#include<cstring>
using namespace std;
const int v=10000+5;const int e=50000+5;
struct arr{int went,next;}a[e];
int begin[v],end[v],b[v],dfs[v],low[v],f[v];
bool flag[v],zhan[v],bre,ff[v];
int deep,step,n,m,i,x,y,cnt,j,go,temp,ans;
void make_up(int u,int v)                    //边表
{
  a[++cnt].went=v;
  if (begin[u]==0) {begin[u]=cnt;end[u]=cnt;}
  else {a[end[u]].next=cnt;end[u]=cnt;}
}
int get(int u){if (u==f[u]) return u;return f[u]=get(f[u]);}  //并查集
void Union(int u,int v){int uu=get(u);int vv=get(v);f[vv]=uu;}
void print(int k)                          //缩点
{
  int fa=b[deep];zhan[fa]=false;
  deep--;if (fa==k) return;
  while (1)
  {
    int i=b[deep];
    Union(fa,i);
    zhan[i]=false;
    deep--;
    if (i==k) break;
  }
}
void find(int k)                           //Tarjan
{
  dfs[k]=low[k]=++step;
  flag[k]=true;b[++deep]=k;zhan[k]=true;
  for (int i=begin[k];i!=0;i=a[i].next)
  {
    int go=a[i].went;
    if (!flag[go])
    {
      find(go);if (low[go]<low[k]) low[k]=low[go];
    }
    if ((zhan[go])&&(dfs[go]<low[k])) low[k]=dfs[go];
  }
  if (dfs[k]==low[k]) print(k);
}
int main()
{
  freopen("popular.in","r",stdin);
  freopen("popular.out","w",stdout);
  scanf("%ld%ld",&n,&m);
  for (i=1;i<=m;i++)
  {
    scanf("%ld%ld",&x,&y);
    make_up(x,y);
  }
  step=0;deep=1;b[1]=1;zhan[1]=true;
  for (i=1;i<=n;i++) f[i]=i;
  for (i=1;i<=n;i++)
    if (!flag[i]) find(i);
  cnt=0;
  for (i=1;i<=n;i++)
  {
    j=begin[i];bre=false;
    while (j!=0)
    {
      go=a[j].went;
      if (get(i)!=get(go)) {bre=true;break;}
      j=a[j].next;
    }
    if (bre) ff[get(i)]=true;
  }
  for (i=1;i<=n;i++)
    if ((f[i]==i)&&(!ff[i])) 
    {
      cnt++;temp=i;
    }
  if (cnt!=1) {printf("0");return 0;}
  for (i=1;i<=n;i++)
    if (get(i)==temp) ans++;
  printf("%ld",ans);
  return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值