bzoj 2438 [中山市选2011]杀人游戏(SCC+概率)

时间:2023-03-09 06:57:38
bzoj 2438 [中山市选2011]杀人游戏(SCC+概率)

【题目链接】

http://www.lydsy.com/JudgeOnline/problem.php?id=2438

【题意】

N个人中有一个杀手,每次询问一个人可能被杀或被告知其认识的人中谁是杀手谁是平民,问不被杀的情况下知道谁是杀手的概率。

【思路】

对于一个scc,如果我们询问一个不是杀手的人,就可以成功遍历整个scc。

求scc,然后缩点。

对于每一个入度为0的scc,我们需要去询问一下,那么被杀的概率为1/n * ans。ans为入度为0的scc个数。

但还有一种特殊的情况,如果一个scc入度为0,但它的所有出度的入度皆大于1,那么它的出度可以不通过询问这个scc得知,而且最终通过其他n-1个点推出这个点的情况。这时候需要对ans-1,但这种情况只计算一次。

【代码】

 #include<set>
#include<cmath>
#include<queue>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define trav(u,i) for(int i=front[u];i;i=e[i].nxt)
#define FOR(a,b,c) for(int a=(b);a<=(c);a++)
using namespace std; typedef long long ll;
const int N = 2e5+;
const int M = 7e5+; ll read() {
char c=getchar();
ll f=,x=;
while(!isdigit(c)) {
if(c=='-') f=-; c=getchar();
}
while(isdigit(c))
x=x*+c-'',c=getchar();
return x*f;
} struct Edge {
int v,nxt;
}e[M];
int en=,front[N];
void adde(int u,int v)
{
e[++en]=(Edge){v,front[u]}; front[u]=en;
} int pre[N],lowlink[N],scc_cnt,sccno[N],S[N],top,dfn; void tarjan(int u)
{
pre[u]=lowlink[u]=++dfn;
S[++top]=u;
trav(u,i) {
int v=e[i].v;
if(!pre[v]) {
tarjan(v);
lowlink[u]=min(lowlink[v],lowlink[u]);
} else
if(!sccno[v])
lowlink[u]=min(lowlink[u],pre[v]);
}
if(pre[u]==lowlink[u]) {
++scc_cnt;
for(;;) {
int x=S[top--];
sccno[x]=scc_cnt;
if(x==u) break;
}
}
} int cnt[N],in[N],n,m,ans; int main()
{
n=read(),m=read();
int u,v;
FOR(i,,m) {
u=read(),v=read();
adde(u,v);
}
FOR(i,,n) if(!pre[i]) tarjan(i);
FOR(u,,n) {
cnt[sccno[u]]++;
trav(u,i) {
int v=e[i].v;
if(sccno[u]!=sccno[v])
in[sccno[v]]++;
}
}
FOR(i,,scc_cnt) if(!in[i]) ans++;
int flag=;
FOR(i,,n)
if(cnt[sccno[i]]==&&in[sccno[i]]==) {
int f=;
trav(i,j) {
int v=e[j].v;
if(in[sccno[v]]<=) { f=; break; }
}
if(f) {
flag=; break;
}
}
ans-=flag;
printf("%.6lf\n",(double)(n-ans)/n);
return ;
}