【bzoj2438】[中山市选2011]杀人游戏 Tarjan

题目描述

一位冷血的杀手潜入 Na-wiat,并假装成平民。警察希望能在 N 个人里面,查出谁是杀手。警察能够对每一个人进行查证,假如查证的对象是平民,他会告诉警察,他认识的人, 谁是杀手, 谁是平民。 假如查证的对象是杀手, 杀手将会把警察干掉。现在警察掌握了每一个人认识谁。每一个人都有可能是杀手,可看作他们是杀手的概率是相同的。问:根据最优的情况,保证警察自身安全并知道谁是杀手的概率最大是多少?

输入

第一行有两个整数 N,M。 
接下来有 M 行,每行两个整数 x,y,表示 x 认识 y(y 不一定认识 x) 。

输出

仅包含一行一个实数,保留小数点后面 6 位,表示最大概率。

样例输入

5 4
1 2
1 3
1 4
1 5

样例输出

0.800000


题解

Tarjan

显然只需要查证所有 缩点后入度为0的强连通分量中的任意一个点 即可,即必须查证的人的个数等于入度为0的强连通分量个数。

但是这并不一定是最优解。考虑一种情况:

这时只需要查证2(或者查证1)即可,可以不查证1(或2)。

具体原因是:一个入度为0的强连通分量大小为1,如果它指向的所有点都不仅由它到达(即减去它到其的边数后入度不为0),那么可以先查证其它点,直到最后仅剩下这个点,即可不查证该点。

于是需要再统计一下是否有这种情况。具体方法:枚举每个点,判断它所有能够到达的点是否仅由它到达即可。注意这样的点只能保留1个(多了无法排除),因此需要及时终止循环。

最后 (n-必须查证的人)/n 即为存活概率。

时间复杂度$O(n+m)$

#include <cstdio>
#include <algorithm>
#define N 100010
#define M 300010
using namespace std;
int head[N] , to[M] , next[M] , cnt , deep[N] , low[N] , tot , vis[N] , ins[N] , sta[N] , top , bl[N] , si[N] , num , ind[N];
inline void add(int x , int y)
{
	to[++cnt] = y , next[cnt] = head[x] , head[x] = cnt;
}
void tarjan(int x)
{
	int i;
	deep[x] = low[x] = ++tot , vis[x] = ins[x] = 1 , sta[++top] = x;
	for(i = head[x] ; i ; i = next[i])
	{
		if(!vis[to[i]]) tarjan(to[i]) , low[x] = min(low[x] , low[to[i]]);
		else if(ins[to[i]]) low[x] = min(low[x] , deep[to[i]]);
	}
	if(deep[x] == low[x])
	{
		int t;
		num ++ ;
		do
		{
			t = sta[top -- ] , si[num] ++ ;
			ins[t] = 0 , bl[t] = num;
		}while(t != x);
	}
}
bool judge(int x)
{
	int i;
	bool flag = 1;
	for(i = head[x] ; i ; i = next[i]) ind[bl[to[i]]] -- ;
	for(i = head[x] ; i ; i = next[i])
		if(!ind[bl[to[i]]])
			flag = 0;
	for(i = head[x] ; i ; i = next[i]) ind[bl[to[i]]] ++ ;
	return flag;
}
int main()
{
	int n , m , i , x , y , ans = 0;
	scanf("%d%d" , &n , &m);
	for(i = 1 ; i <= m ; i ++ ) scanf("%d%d" , &x , &y) , add(x , y);
	for(i = 1 ; i <= n ; i ++ )
		if(!vis[i])
			tarjan(i);
	for(x = 1 ; x <= n ; x ++ )
		for(i = head[x] ; i ; i = next[i])
			if(bl[x] != bl[to[i]])
				ind[bl[to[i]]] ++ ;
	for(i = 1 ; i <= num ; i ++ )
		if(!ind[i])
			ans ++ ;
	for(i = 1 ; i <= n ; i ++ )
		if(si[bl[i]] == 1 && !ind[bl[i]] && judge(i))
			break;
	if(i <= n) ans -- ;
	printf("%.6lf\n" , (double)(n - ans) / n);
	return 0;
}

 

 

posted @ 2017-10-24 08:20  GXZlegend  阅读(388)  评论(0编辑  收藏  举报