[BZOJ 1051][POJ 2186] 受欢迎的牛/Popular Cows Tarjan缩点+判断出度

题目传送门:【BZOJ 1051】 【POJ 2186】 (两道题是一样的)

题目大意:每一头牛的愿望就是变成一头最受欢迎的牛。现在有 N 头牛,给你 M 对整数 ( A , B ),表示牛 A 认为牛 B 受欢迎。 这

种关系是具有传递性的,如果 A 认为 B 受欢迎,B 认为 C 受欢迎,那么牛 A 也认为牛 C 受欢迎。你的任务是求出有多少头
牛被所有的牛认为是受欢迎的。(0 ≤ N ≤ 10000 , 0 ≤ M ≤ 50000 , ( A , B ) 可能有重复)

题目分析:
一道非常经典的求强连通分量以及缩点的题。许多 PPT 讲稿里面都会出现这道题。
由题,我们把每头牛抽象成一个点,牛与牛之间的关系抽象成单向边。这道题要求判断能够被“其他所有的点到达”的点的数量。
分析题意,只有当一个点的出度为 0,并且与所有其他点连通时才符合题目要求。注意到这个点可以只是一个“点”,也可以是“缩点”之后形成的点。因此,我们首先跑一遍 Tarjan,求出这个图的强连通分量;之后对每个强连通分量缩点,形成一个 DAG,之后根据这个 DAG 求出出度为 0 的点的数量。如果数量恰好为 1,那么答案就是这个点(同上,这个点既可以是一个“点”,也可以是一个强连通分量;前者答案为 1,后者答案为强连通分量的大小);反之,如果数量大于 1,则说明肯定有些点不连通(也就是说一头牛总是得不到某些牛的欢迎),那么此时答案为 0。

缩点的方法:求出强连通分量后,设每个点都有一个 belong[ i ];枚举 1-n 的点,判断和它相连的点 j 的 belong[ ] 是否相等;如果相等,说明在同一强连通分量中,跳过;否则,说明它们不在同一个强连通分量中,那么对它们连一条边。

下面附上代码:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<stack>
using namespace std;
const int MX=100005;

struct Edge{
	int to,len,next;
};
Edge edge[5*MX];
int n,m,head[MX],now=0;
int low[MX],dfn[MX],_index=0,belong[MX],_size[MX];//_size:这个强连通分量的大小 
int out[MX],cnt=0;					//out:出度大小  cnt:强连通分量的数量 
stack<int> s;
bool ins[MX];

void adde(int u,int v){
	edge[++now].to = v;
	edge[now].next = head[u];
	head[u] = now;
}

void tarjan(int u){
	low[u] = dfn[u] = ++_index;
	s.push(u);
	ins[u] = true;
	for (int i = head[u]; i ;i = edge[i].next){
		int v = edge[i].to;
		if (!dfn[v]){
			tarjan(v);
			if (low[v] < low[u]){
				low[u] = low[v];
			}
		} else if (ins[v] && dfn[v] < low[u]){
			low[u] = dfn[v];
		}
	}
	if (low[u] == dfn[u]){
		cnt++;
		int tmp;
		do{
			tmp = s.top();
			s.pop();
			belong[tmp] = cnt;
			ins[tmp] = false;
		}while (tmp != u);
	}
}

void solve(){
	for (int i = 1;i <= n;i++){
		_size[belong[i]]++;
	}
	for (int i = 1;i <= n;i++){
		for (int j = head[i]; j ;j = edge[j].next){
			int v = edge[j].to;
			if (belong[i] != belong[v]){
				out[belong[i]]++;
			}
		}
	}
}

int main(){
	int a,b;
	cin>>n>>m;
	if (m == 0){							//没有牛认为其他的牛受欢迎,直接退出 
		printf("0");
		return 0;
	}
	for (int i = 1;i <= m;i++){
		cin>>a>>b;
		adde(a,b);
	}
	for (int i = 1;i <= n;i++){
		if (!dfn[i])
			tarjan(i);
	}
	solve();
	
	int flag = 0;
	bool used = false;
	for (int i = 1;i <= cnt;i++){
		if (out[i] == 0){
			if (used){
				printf("0");
				return 0;
			} else {
				flag = i;
				used = true;
			}
		}
	}
	printf("%d",_size[flag]);
	return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值