杀人游戏「Tarjan缩点」

杀人游戏「Tarjan缩点」

题目描述

​ 一位冷血的杀手潜入Na-wiat,并假装成平民。警察希望能在N个人里面,查出谁是杀手。警察能够对每一个人进行查证,假如查证的对象是平民,他会告诉警察,他认识的人,谁是杀手,谁是平民。假如查证的对象是杀手,杀手将会把警察干掉。现在警察掌握了每一个人认识谁。每一个人都有可能是杀手,可看作他们是杀手的概率是相同的。

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

输入输出格式

输入格式:

​ 第一行有两个整数 N,M。 接下来有 M 行,每行两个整数 x,y,表示 x 认识 yy 不一定认识 x ,例如President同志) 。

输出格式:

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

输入输出样例

输入样例:

5 4 
1 2 
1 3 
1 4 
1 5 

输出样例:

0.800000

说明

​ 警察只需要查证1。假如1是杀手,警察就会被杀。假如1不是杀手,他会告诉警察2,3,4,5谁是杀手。而1是杀手的概率是0.2,所以能知道谁是杀手但没被杀的概率是0.8。


思路分析

  • 根据题目信息,知道一个平民点后,可以得到所有与它有关的点的信息,这样就会形成强连通分量,所有给出信息的点也都可以遍历一遍,自然而然地就需要缩点。
  • 杀手点肯定是不会与任何点连通的,因为如果访问杀手点以后警察就无了。那我们根据排除法即可找出杀手的可能。
  • 另外还有一种特殊情况就是我们已经安全地访问了n-1个点,那么最后的那个点就不需要再访问,因为它一定是杀手,这样就少了一次访问次数

代码

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
const int M = 3e5+10,N = 1e5+10;
int head[N],dfn[N],low[N],belong[N],sta[N],siz[N],ru[N],x[M],y[M],dfs_clock,scc_cnt,top;
using namespace std;
struct edge{
	int to,next;
}e[M];
int len;
void addedge(int u,int v){
	e[++len].to = v;
	e[len].next = head[u];
	head[u] = len;
}
void Tarjan(int u){
	dfn[u] = low[u] = ++dfs_clock;
	sta[++top] = u;
	for(int i = head[u];i;i=e[i].next){
		int v = e[i].to;
		if(!dfn[v]){
			Tarjan(v);
			low[u] = min(low[u],low[v]);
		}
		else if(!belong[v])low[u]=min(low[u],dfn[v]);
	}
	if(dfn[u]==low[u]){
		scc_cnt++;
		while(1){
			int x = sta[top--];
			belong[x] = scc_cnt;
			siz[scc_cnt]++;
			if(x==u)break;
		}
	}
}
int main(){
	/*freopen("data.in","r",stdin);
	freopen("my.out","w",stdout);*/
	int n,m;scanf("%d%d",&n,&m);
	for(int i = 1;i <= m;i++){
		scanf("%d%d",&x[i],&y[i]);
		addedge(x[i],y[i]);
	}
	for(int i = 1;i <= n;i++){
		if(!dfn[i])Tarjan(i);
	}
	for(int i = 1;i <= m;i++){//缩点操作
		if(belong[x[i]]!=belong[y[i]]){//不在同一个强连通分量里
			ru[belong[y[i]]]++; //终点所在强连通分量入度+1
			addedge(belong[x[i]],belong[y[i]]);//两个强连通分量连起来
		}
	}
	int ans = 0,flag = 0; //flag判断是否是杀手
	for(int i = 1;i <= scc_cnt;i++){
		if(!flag&&!ru[i]&&siz[i]==1){//是否为杀手
			int pd = 0;
			for(int j = head[i];j;j = e[j].next){
				int v = e[j].to;
				if(ru[v]==1)pd=1;//可以到达其他点说明并不是杀手
			}
			if(!pd)flag = 1; //是杀手
		}
		if(!ru[i])ans++; //不是杀手且无法通过其他强连通分量到达的点,需要询问一次
	}
	if(flag)ans--; //通过排除法少了一次访问次数
	printf("%.6f\n",1.0-(double)ans/(double)n);//每次访问都可能直接访问到凶手,ans/n为警察有危险的概率
	return 0;
}
posted @ 2020-07-17 16:32  HH_Halo  阅读(146)  评论(0编辑  收藏  举报