https://www.lydsy.com/JudgeOnline/problem.php?id=2438
一开始以为是一道大水题,后来发现有些地方还是要想清楚才能写出来
首先Tarjan缩点
在不清楚的情况下肯定不选入度大于等于1的SCC询问,因为他们中有没有杀手可以通过其前驱知道,没必要冒险
而稍微推一下就知道,每次选择一个入度为0的SCC询问的风险是$\frac{1}{n}$
所以就统计有多少个入度为0的SCC就行了?
不,有一种特殊情况,假如已经调查了$n-1$个人,剩下一个人肯定是杀手
所以我们尽量使它出现这样的情况,$f_i$表示到达某个SCC的方案数
如果对于某个只含一个节点且入度为0的SCC,它的后继节点全都有至少两种方式到达,那么我们可以把它最为剩下的一个人
#include<bits/stdc++.h> using namespace std; const int N=1e5+5,M=3*(1e5+5); int n,m; int head[N],ver[M],nxt[M],ce; int dfn[N],cnt,low[N],c[N],cd,st[N],top; int deg[N],sz[N],f[N]; vector<int> to[N]; bool ins[N],mk[N]; int ans; void tarjan(int now) { dfn[now]=low[now]=++cnt,st[++top]=now,ins[now]=1; for(int i=head[now];i;i=nxt[i]) { int y=ver[i]; if(!dfn[y]) { tarjan(y); low[now]=min(low[now],low[y]); } else if(ins[y]) low[now]=min(low[now],dfn[y]); } if(dfn[now]==low[now]) { int y=-1; ++cd; while(y!=now) y=st[top--],ins[y]=0,c[y]=cd,++sz[cd]; } } void adde(int a,int b) { ver[++ce]=b,nxt[ce]=head[a],head[a]=ce; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=m;++i) { int u,v; scanf("%d%d",&u,&v); adde(u,v); } for(int i=1;i<=n;++i) if(!dfn[i]) tarjan(i); for(int i=1;i<=n;++i) for(int j=head[i];j;j=nxt[j]) { int y=ver[j]; if(c[y]!=c[i]) { ++deg[c[y]]; to[c[i]].push_back(c[y]); } } queue<int> q; for(int i=1;i<=cd;++i) if(!deg[i]) {q.push(i);++ans,f[i]=mk[i]=1;} while(!q.empty()) { int u=q.front(); q.pop(); for(int i=0;i<to[u].size();++i) { int v=to[u][i]; f[v]+=f[u],--deg[v]; if(!deg[v]) q.push(v); } } bool flag=0; for(int i=1;i<=cd;++i) if(mk[i] && sz[i]==1) { flag=1; for(int j=0;j<to[i].size();++j) if(f[to[i][j]]==1) {flag=0; break;} if(flag) break; } if(flag) --ans; ans=n-ans; printf("%.6lf",(double)(ans)/(double)(n)); return 0; }