【luogu4819】 [中山市选]杀人游戏 [tarjan 缩点]
显然问的人越少越好,若问了k个人,答案为(n-k)/n
若点x有入边,询问x的前驱比询问x要好;但如果有环,区分有没有从环外指向环内的边,若有进入环的边,……,若没有,……,所以可以先缩点
缩环之后图会变成拓扑图,只需要每个入度为0的点进行一次询问即可得知凶手,所以答案为(n-入度为0的点)/n
需要注意的是如果存在某个入度为0的点,其儿子块可由别的询问确定,可以少询问一次……增加1/n的概率
需要注意的是如果存在某个入度为0的点,其儿子块可由别的询问确定,可以少询问一次……增加1/n的概率
#include<bits/stdc++.h> using namespace std; #define Max(x,y) (x)>(y)?(x):(y) #define Min(x,y) (x)>(y)?(y):(x) #define ll long long #define rg register const int N=1000000+5,M=10000+5,inf=0x3f3f3f3f,P=99999997; map<string,int>a; int n,m,ans=0,in[N]; int dfn[N],low[N],bl[N],size[N],Bcnt=0,idx=0; bool inst[N]; stack<int>s; template <class t>void rd(t &x){ x=0;int w=0;char ch=0; while(!isdigit(ch)) w|=ch=='-',ch=getchar(); while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); x=w?-x:x; } int head[N],tot=0; struct edge{int u,v,nxt;}e[N<<1]; void add(int u,int v){ e[++tot]=(edge){u,v,head[u]},head[u]=tot; } void tarjan(int u){ dfn[u]=low[u]=++idx; s.push(u),inst[u]=1; for(int i=head[u],v;i;i=e[i].nxt){ v=e[i].v; if(!dfn[v]) tarjan(v),low[u]=Min(low[u],low[v]); else if(inst[v]&&dfn[v]<low[u]) low[u]=dfn[v]; } if(low[u]==dfn[u]){ int v;++Bcnt; do{ v=s.top(),s.pop(); bl[v]=Bcnt,inst[v]=0,++size[Bcnt]; }while(v!=u); } } int main(){ // freopen("in.txt","r",stdin); rd(n),rd(m); for(int i=1,u,v;i<=m;++i) rd(u),rd(v),add(u,v); for(int i=1;i<=n;++i) if(!dfn[i]) tarjan(i); memset(head,0,sizeof(head));tot=0; for(int i=1;i<=m;++i) if(bl[e[i].u]!=bl[e[i].v]) ++in[bl[e[i].v]],add(bl[e[i].u],bl[e[i].v]); int flag=0; // for(int i=1;i<=Bcnt;++i)printf("%d,%d ",in[i],size[i]); for(int i=1;i<=Bcnt;++i){ if(!flag&&!in[i]&&size[i]==1){ int fl=0; for(int j=head[i];j;j=e[j].nxt) if(in[e[j].v]==1) fl=1; if(!fl) flag=1; } if(!in[i]) ++ans; } if(flag) --ans; // printf("%d %d",flag,ans); printf("%.6f",(double)(n-ans)/n); return 0; }