【图论·习题】杀人游戏:Tarjan强连通分量

Problem

Description
一位冷血的杀手潜入 Na-wiat,并假装成平民。警察希望能在 N 个人里面,查出谁是杀手。警察能够对每一个人进行查证,假如查证的对象是平民,他会告诉警察,他认识的人, 谁是杀手, 谁是平民。

假如查证的对象是杀手, 杀手将会把警察干掉。现在警察掌握了每一个人认识谁。每一个人都有可能是杀手,可看作他们是杀手的概率是相同的。

问:根据最优的情况,保证警察自身安全并知道谁是杀手的概率最大是多少?

Input
第一行有两个整数 N,M。

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

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

题解

这道题我们很容易就发现,对于一个强连通分量来说:若询问的人不是杀手,则可以在不死的情况下知道一个谁是凶手;因为可以遍历到每一个人,且不是凶手就继续遍历;知道找到凶手。且在这一个强连通分量内任意询问那个点均可。

因此使用强连通分量缩点即可。

在完成缩点操作以后,我们需要找到每一个入度为0的点;因为找到入度为0的点,若当前点不是凶手就可以把它所能连接到的所有点都遍历到,即可以查清它所能到达的所有人内是否有凶手;因为需要全部查清,所以如要通过每一个入度为0的点进行查找。此时在缩点后如果入度为0的点有s个,则输出的答案是: a n s   =   1   −   s n ans\ =\ 1\ -\ \frac{s}{n} ans = 1  ns
此时你一定有一个问题,那就是不能够通过某一个点遍历所有点吗?即为什么一定要所有的入度为0的点来统计答案;入度为0的就是其他点无法达到,所有每一个入度为0的点都需要统计。

有一个特殊的情况就是:
若当前存在一个点,不会通过其它点遍历到;但是可以通过遍历其它n-1个点,通过排除法来确认关系,此时这个点满足一下条件:

  • 这个点是被孤立的,入度为0。
  • 这个点所连接的点一定会被其它的点遍历到,即入度一定大于1。

这是答案是: a n s   =   1   −   s − 1 n ans\ =\ 1\ -\ \frac{s-1}{n} ans = 1  ns1

代码如下:

#include <bits/stdc++.h>
using namespace std;
int n,m,cnt=0,num=0,tot=0,top=0;
int v[500000];
int c[500000];
int in[500000];
int st[500000];
int dfn[500000];
int out[500000];
int low[500000];
int Link[500000];
vector<int>a[500000];
struct edge {
	int y,next;
}e[500000];
void add(int a,int b)
{
	tot++;
	e[tot].y=b;
	e[tot].next=Link[a];
	Link[a]=tot;
}
void tarjan(int x)
{
	dfn[x]=low[x]=++cnt;
	st[++top]=x,in[x]=1;
	for (int i=Link[x];i;i=e[i].next)
	{
		if (!dfn[e[i].y])
		{
			tarjan(e[i].y);
			low[x]=min(low[x],low[e[i].y]);
		}
		else if (in[e[i].y]) 
		    low[x]=min(low[x],dfn[e[i].y]);
	}
	if (low[x] == dfn[x])
	{
		num++;
		int y;
		while (x!=y)
		{
			y=st[top];
			top--;
			in[y]=0;
			c[y]=num;  
		}
	}
}
int main(void)
{
	freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);
	scanf("%d %d",&n,&m);
    if (m == 0)
    {
    	printf("%.6f",1.0/(n*1.0));
    	return 0;
	}
	for (int i=1,x,y;i<=m;++i)
	{
		scanf("%d %d",&x,&y);
		add(x,y);
	}
	for (int i=1;i<=n;++i)
	    if (!dfn[i]) tarjan(i);
	for (int i=1;i<=n;++i) 
	    v[c[i]]++;
	for (int i=1;i<=n;++i)
	    for (int j=Link[i];j;j=e[j].next)
	        if (c[i]!=c[e[j].y])
	        {
	            in[c[e[j].y]]++,out[c[i]]++;
	            a[c[i]].push_back(c[e[j].y]);
	        }
	int s=0,flag=0;
	for (int i=1;i<=num;++i)
	{
		if (!in[i]) s++;
	    if (!in[i] && v[i]==1)
	    {
	    	int flag_=1;
	    	for (int j=0;j<a[i].size();++j) 
	    	    if (in[a[i][j]]<=1) 
	    	    {
	    	    	flag_=0;
	    	    	break;
				}
	    	flag|=flag_;
		}
	}
	if (flag) printf("%.6f",1.0-(s*1.0-1)/(n*1.0));
	else printf("%.6f",1.0-(s*1.0)/(n*1.0));
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值