杀人游戏「Tarjan缩点」
杀人游戏「Tarjan缩点」
题目描述
一位冷血的杀手潜入Na-wiat,并假装成平民。警察希望能在N
个人里面,查出谁是杀手。警察能够对每一个人进行查证,假如查证的对象是平民,他会告诉警察,他认识的人,谁是杀手,谁是平民。假如查证的对象是杀手,杀手将会把警察干掉。现在警察掌握了每一个人认识谁。每一个人都有可能是杀手,可看作他们是杀手的概率是相同的。
问:根据最优的情况,保证警察自身安全并知道谁是杀手的概率最大是多少?
输入输出格式
输入格式:
第一行有两个整数 N
,M
。 接下来有 M
行,每行两个整数 x
,y
,表示 x
认识 y
(y
不一定认识 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;
}