POJ 2186 Popular Cows (tarjan + 缩点)

                               Popular Cows
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. 

题目分析

和图相关的知识中,有一些重要的概念:

在有向图G中,如果两点互相可达,则称这两个点强连通,如果G中任意两点互相可达,则称G是强连通图。

定理: 1、一个有向图是强连通的,当且仅当G中有一个回路,它至少包含每个节点一次。

            2、非强连通有向图的极大强连通子图,称为强连通分量(SCC即Strongly Connected Componenet)。

对于这个题目,我们发现强连通分量是很有意义的:强连通分量中的每个点之间都是互相可达到的,而且我们还可以把整个图划分为数个强连通分量,不同的强连通分量之间可能存在到达关系,那么我们可以将每一个强连通分量都当作一个点,这样一来,强连通分量以及强连通分量包含的点之间的边就构成了 DAG图(有向无环图),而DAG图中有一个特点。任意两点之间的边是单向的,而且图中无环(DAG图的性质很重要,后面说明)。那么为什么这样可以形成DAG图呢?

我们知道强连通分量的一个性质是:强连通分量中的任意两点之间是相互可达的,这就是不成环的原因,因为要是成环了,那么这个环就是一个更大的强连通分量了呀(DAG图构建如下,把每一个红圈当作一个点,这样就是DAG图了)。

现在我们任意画出一个DAG图(如下):

观察一下,发现这个图没有满足题目要求的强连通分量,因为不存在一个可以由其余各点到达的点,而此时我们发现,图中存在两个出度为0的点,这说明了什么?一个出度为0的点不可能到达其余任何一点,而如果出现两个及两个以上的点的出度为0,那么显然,不可能出现一个可以由其余各点到达的点,所以度数为0的点不可以是两个及两个以上。

那么度数为0的点是0个,又会怎么样呢?此时我们就要利用DAG图的性质了,如果DAG图不存在入度为0的点,那么这个图中必然存在环,所以我们将强连通分量缩成点之后得到的DAG图中,度数为0的点至少是一个(下面的图就满足了)。

综上所述,我们将强连通分量缩成点之后得到DAG图,如果这个图中有且仅有一个出度为0的点,记这个点代表的强连通分量G,G中的所有点都可以由其余所有的点到达,也就是G中的牛被所有的牛喜爱,我们所求的就是G中点的个数即可。

而求强连通分量的一个方法就是用tarjan算法,这个是很常见的操作了,就不多做叙述。

代码区

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<string>
#include<cmath>
#include<fstream>
#include<vector>
#include<stack>
#include <map>
#include <iomanip>
#define bug cout << "**********" << endl
#define show(x,y) "["<<x<<","<<y<<"] "
//#define LOCAL = 1;
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int mod = 1e8;
const int Max = 5e4 + 10;

struct Edge
{
	int to, next;
}edge[Max<<1];

int n, m;
int head[Max], tot;
int dfn[Max], low[Max], time_clock;			//dfn[i]  : i点的时间戳 ; low: i点的子树的最小时间戳; time_clock: 控制时间戳的
int line[Max],now;							//line[i] : 记录访问次序; now控制访问的结点
int node[Max],num[Max],ans;					//node[i] : 记录i属于哪一个强连通分量; num[i]记录第i个强连通分量中点的个数; ans控制强连通分量的标识和数量
int out_d[Max];								//out_d[i]: 记录第i个强连通分量的出度

void init()
{
	memset(head, -1, sizeof(head)), tot = 0;
	memset(dfn, 0, sizeof(dfn)), time_clock = 0;
	now = 0;								//line直接赋值,无需初始化
	memset(node, 0, sizeof(node)), ans = 0;	//num直接赋值,无需初始化
	memset(out_d, 0, sizeof(out_d));
}

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

void tarjan(int u)
{
	dfn[u] = low[u] = ++time_clock;
	line[++now] = u;		//记录访问次序
	for(int i = head[u]; i != -1 ; i = edge[i].next)
	{
		int v = edge[i].to;
		if(!dfn[v])
		{
			tarjan(v);
			low[u] = min(low[u], low[v]);
		}
		else if(!node[v])		//不属于其他的连通分量,如果属于其他的强连通分量,那就破坏了此时不求强连通分量之间的关系的计划
		{
			low[u] = min(low[u], dfn[v]);
		}
	}
	if(dfn[u] == low[u])		//此时代表从u向下走,又回到了u点,也就是成环了,我们将这一点定义为强连通分量的根结点
	{
		int e = now;			//这一强连通分量访问的最远点(类比一下)
		ans++;
		while (line[now] != u)	//同一个强连通分量中的点用同一个数标识
			node[line[now]] = ans, now--;
		node[line[now]] = ans, now--;
		num[ans] = e - now;		//记录这一强连通分量中点的个数
	}
}

int main()
{
#ifdef LOCAL
	freopen("input.txt", "r", stdin);
	freopen("output.txt", "w", stdout);
#endif
	while (scanf("%d%d", &n, &m) != EOF)
	{
		init();
		for (int i = 1;i <= m;i++)
		{
			int u, v;
			scanf("%d%d", &u, &v);
			add(u, v);
		}
		for (int i = 1; i <= n; i++) if (!dfn[i]) tarjan(i);
		for(int u = 1;u <= n ; u++)
		{
			for(int i = head[u] ; i != -1 ; i = edge[i].next)
			{
				int v = edge[i].to;
				if (node[u] != node[v])	//不同的点之间的边
					out_d[node[u]]++;
			}
		}
		int sum = 0;
		for(int i = 1 ;i <= ans;i++)
		{
			if(out_d[i] == 0)			//判断是否是出度为0的点
			{
				if (sum != 0)			//sum被赋予了非0的值,说明至少出现了2个出度为0点
				{
					printf("0\n");
					return 0;
				}
				else
					sum = num[i];		//第一个度数为0点
			}
		}
		printf("%d\n", sum);			//这里不可能输出0的,因为DAG图性质
	}

	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值