一开始想到用并查集直接求联通,但是这样貌似并不可行。。。
每调查一个人,都会增加1/n的被杀概率,所以要尽量少调查人。
如果我们调查了一个人,那么和他认识的人我们全都能知道,以此类推,向下的人都可以知道,所以我们只要求入度为0的点的个数,这些人我们只能通过直接询问他才能知道是不是杀手,另外的人都可以通过这些人知道。
对于一个环里的人,我们只要知道一个点就能摸清所有点,所以先Tarjan缩环。
另外这题还有一个特别精巧的地方,这n个点构成了scc个分量,有一个分量只包含一个点,如果这个分量没有出边,或者他连向的点都可以通过其他分量到达,那么我们只要查其他scc-1个点就可以摸清n-1个点,最后一个点自然也清楚了,这时候要不算这个点的询问次数。(只有可能存在一个点,因为知道n-2个点并不能摸清剩下2个点)
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <iostream>
using namespace std;
const int N=300005;
struct arr{
int node,nxt;
}e1[N],e2[N];
int head1[N],head2[N],tot1,tot2;
int n,m,sumin[N],sumout[N],ans,num[N];
void add1(int x,int y){
e1[++tot1].node=y;
e1[tot1].nxt=head1[x];
head1[x]=tot1;
}
void add2(int x,int y){
e2[++tot2].node=y;
e2[tot2].nxt=head2[x];
head2[x]=tot2;
sumin[y]++;sumout[x]++;
}
int dfn[N],low[N],tim,stack[N],top,scc,belong[N];
bool check[N];
void tarjan(int t){
dfn[t]=low[t]=++tim;
check[t]=1; stack[++top]=t;
int u,v;
for (int i=head1[t];i;i=e1[i].nxt){
int x=e1[i].node;
if (!dfn[x]) {
tarjan(x);
low[t]=min(low[t],low[x]);
}
else
if (check[x]) low[t]=min(low[t],dfn[x]);
}
if (dfn[t]==low[t]){
int now=0;scc++;
while (now!=t){
now=stack[top--];
belong[now]=scc;
check[now]=0;
num[scc]++;
}
}
}
void rebuild(){
bool vis[N];
memset(vis,0,sizeof vis); //防止加重边
for (int i=1;i<=n;i++){
for (int j=head1[i];j;j=e1[j].nxt){
int u=belong[i],v=belong[e1[j].node];
if (u!=v && !vis[v]) {vis[v]=1;add2(u,v);}
}
for (int j=head1[i];j;j=e1[j].nxt){
int u=belong[i],v=belong[e1[j].node];
if (u!=v) vis[v]=0;
}
}
}
bool judge(int i){
if (sumin[i] || num[i]!=1) return 0; //入度不为0或者联通块中不止一个点则跳出
for (int j=head2[i];j;j=e2[j].nxt)
if (sumin[e2[j].node]==1) return 0;
return 1;
}
int main(){
cin>>n>>m;
for (int i=1;i<=m;i++) {
int x,y;cin>>x>>y;
add1(x,y);
}
for (int i=1;i<=n;i++)
if (!dfn[i]) tarjan(i);
rebuild();
for (int i=1;i<=scc;i++)
if (!sumin[i]) ans++;
for (int i=1;i<=scc;i++)
if (judge(i)) {
ans--;break;
}
printf("%.6lf\n",(n-ans)*1.0/n);
return 0;
}