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 − ns−1
代码如下:
#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));
}