POJ 2186 Popular Cows(强连通分量)
Popular Cows
Time Limit: 2000MS | Memory Limit: 65536K | |
Total Submissions: 13835 | Accepted: 5484 |
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.
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.
* 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
/*
POJ2186
1.求出所有的强连通分量,用Korasaju算法
2.每个强连通分量缩成一点,则形成一个有向无环图DAG。
3.DAG上面如果有唯一的出度为0的点,则改点能被所有的点可达。
那么该点所代表的连通分量上的所有的原图中的点,都能被原图中
的所有点可达 ,则该连通分量的点数就是答案。
4.DAG上面如果有不止一个出度为0的点,则这些点互相不可达,原问题
无解,答案为0;
(此外DAG肯定是一个有向无环图,则至少存在一个出度为0的点,上面
两种情况包括了所有可能。
此题用了邻接表存图,并利用邻接表进行DFS,需要学习下。)
*/
#include<stdio.h>
#include<string.h>
#include<iostream>
using namespace std;
#define MAXN 10005 //结点的最大数
struct Node
{
int to,next;
}edge1[MAXN*5],edge2[MAXN*5];
//edge1用来存原图G,edge2用来存GT,即逆图
//都是用邻接表来储存的图,
int head1[MAXN];//原图的邻接表的头结点
int head2[MAXN];//逆图的邻接表的头结点
int mark1[MAXN],mark2[MAXN];
int tot1,tot2;
int cnt1,cnt2,st[MAXN],belong[MAXN];
int num,setNum[MAXN];
struct Edge
{
int l,r;
}e[MAXN*5];//储存边
void add(int a,int b)//添加边a->b,在原图和逆图都需要添加
{
edge1[tot1].to=b;edge1[tot1].next=head1[a];head1[a]=tot1++;//邻接表存的原图
edge2[tot2].to=a;edge2[tot2].next=head2[b];head2[b]=tot2++;//邻接表存的逆图
}
void DFS1(int x)//深度搜索原图
{
mark1[x]=1;
for(int i=head1[x];i!=-1;i=edge1[i].next)
if(!mark1[edge1[i].to]) DFS1(edge1[i].to);
st[cnt1++]=x;//st数组是按照完成时间从小到大排序的
}
void DFS2(int x)//深度搜索逆图
{
mark2[x]=1;
num++;
belong[x]=cnt2;
for(int i=head2[x];i!=-1;i=edge2[i].next)
if(!mark2[edge2[i].to]) DFS2(edge2[i].to);
}
int main()
{
int n,m;
while(scanf("%d%d",&n,&m)!=EOF)
{
tot1=tot2=1;
for(int i=1;i<=n;i++)//初始化
{
head1[i]=head2[i]=-1;
mark1[i]=mark2[i]=0;
}
for(int i=1;i<=m;i++)
{
int w,v;
scanf("%d%d",&w,&v);
e[i].l=w;e[i].r=v;//储存边
add(w,v);//建立邻接表
}
cnt1=cnt2=1;
for(int i=1;i<=n;i++)
{
if(!mark1[i])DFS1(i);
}
for(int i=cnt1-1;i>=1;i--)
{
if(!mark2[st[i]])
{
num=0;
DFS2(st[i]);
setNum[cnt2++]=num;
}
}
int de[MAXN];//计算出度
memset(de,0,sizeof(de));
for(int i=1;i<=m;i++)//计算各个DAG图的出度
{
if(belong[e[i].l]!=belong[e[i].r])//原图的边不属于同一连通分支
de[belong[e[i].l]]++;
}
//计算DAG出度为0的个数
int cnt=0,res;
for(int i=1;i<cnt2;i++)
if(!de[i]){cnt++;res=i;}
if(cnt>1) printf("0\n");
else printf("%d\n",setNum[res]);
}
return 0;
}
/*
Kosaju算法比较好理解,就是先对逆图作一遍dfs,计算后序编号定义顶点的排列(如果有向图是一个DAG,
这一过程产生一个拓扑排序序列)。然后在图上再一次DFS,不过是按照具有最高后序编号的未访问的顶点的顺
序。
这样得到的就是一个强连通分量了。因为在访问逆图的时候,只有当原图中w点可以到达v点的时候,
逆图中的dfs树中V才可能是W的父节点,也就是说逆图中V可到达w,这样,v的编号将大于w。再在原图中作DFS
,
由于是按照先访问最高后序未访问编号的次序,这样如果在原图中w可以到达v,那么在逆图中就有V可以到达w
*/
#include<cstdio>
#include<cstring>
#include<iostream>
#define M 10005 //结点数
using namespace std;
struct node{
int to,next;
}edge1[M*10],edge2[M*10];
int mark1[M],mark2[M],head1[M],head2[M];
int tot1,tot2;
int cnt1,st[M],cnt2,belong[M];
int num,setNum[M];
struct nn{
int l,r;
}e[M*10];
void add(int a,int b){ //每个点的前端结点 和 后端结点
edge1[tot1].to=b, edge1[tot1].next=head1[a], head1[a]=tot1++;//邻接表存的原图
edge2[tot2].to=a, edge2[tot2].next=head2[b], head2[b]=tot2++;//邻接表存的逆图
}
void DFS1(int x) {
mark1[x]=1;
for(int i=head1[x]; i!=-1; i=edge1[i].next) //正向
if(!mark1[edge1[i].to]) DFS1(edge1[i].to);
st[cnt1++]=x;
}
void DFS2(int x) {
mark2[x]=1;
num++;
belong[x]=cnt2; //正向能到,反向也能到的是在同一联通分量里
for(int i=head2[x]; i!=-1; i=edge2[i].next) //逆向
if(!mark2[edge2[i].to]) DFS2(edge2[i].to);
}
int main() {
int n,m;
while(scanf("%d%d",&n,&m) != EOF){
tot1=tot2=0;
for(int i=0; i<n; i++){
head1[i]=head2[i]=-1;
mark1[i]=mark2[i]=0;
}
for(int i=0; i<m; i++){
int w,v;
scanf("%d%d",&w,&v);
w--, v--; //结点从0开始编号
e[i].l=w; e[i].r=v;
add(w,v);
}
cnt1=cnt2=0;
for(int i=0; i<n; i++){
if(!mark1[i]) DFS1(i);
}
for(int i=cnt1-1; i>=0; i--){
if(!mark2[ st[i] ]) {
num=0;
DFS2(st[i]);
setNum[cnt2++]=num;
}
}
int de[M];
memset(de,0,sizeof(de));
for(int i=0; i<m; i++){
if(belong[ e[i].l ] != belong[ e[i].r ])
de[ belong[e[i].l] ]++;
}
int cnt=0,res;
for(int i=0; i<cnt2; i++){
if(!de[i]) {
cnt++; res=i;
}
}
if(cnt>1) printf("0\n");
else printf("%d\n",setNum[res]);
}
return 0;
}
人一我百!人十我万!永不放弃~~~怀着自信的心,去追逐梦想