[中山市選]殺人游戲 Tarjan+概率
題目描述
 一位冷血的殺手潛入\(Na\)-\(wiat\),並假裝成平民。警察希望能在\(N\)個人里面,查出誰是殺手。警察能夠對每一個人進行查證,假如查證的對象是平民,他會告訴警察,他認識的人,誰是殺手,誰是平民。假如查證的對象是殺手,殺手將會把警察干掉。現在警察掌握了每一個人認識誰。每一個人都有可能是殺手,可看作他們是殺手的概率是相同的。
問:根據最優的情況,保證警察自身安全並知道誰是殺手的概率最大是多少?
輸入輸出格式
輸入格式:
 第一行有兩個整數 \(N,M\)。 接下來有 \(M\) 行,每行兩個整數 \(x,y\),表示 \(x\) 認識 \(y\)(\(y\) 不一定認識 \(x\) ,例如\(President\)同志) 。
輸出格式:
 僅包含一行一個實數,保留小數點后面 \(6\) 位,表示最大概率。
輸入輸出樣例
輸入樣例#1:
5 4 
1 2 
1 3 
1 4 
1 5 
 
        輸出樣例#1:
0.800000
 
        說明
 警察只需要查證\(1\)。假如\(1\)是殺手,警察就會被殺。假如\(1\)不是殺手,他會告訴警察\(2,3,4,5\)誰是殺手。而\(1\)是殺手的概率是\(0.2\),所以能知道誰是殺手但沒被殺的概率是\(0.8\)。
題解
首先想到的是\(Tarjan\)縮點,因為縮點之后我們只需要查詢環中的一個人就可以知道整個環的信息。
而且若一個環又指向另一個環,那么我們要是選擇一個縮點后入度為0的點,即可以把其連通的若干個環的信息全部了解。

但是這樣還有還有一些錯誤:
舉個例子,若一共有\(N\)個人,我們查詢了一些人,使得我們知道了\(N\)-\(1\)個人的身份(都是平民,否則你就掛了),那么剩下的一個人一定是殺手(排除法),那么我們就可以少去查詢一個人。
所以,我們要去尋找一個大小為1且入度為0的點((縮點后)就是一個與世隔絕的人),並且他指向的點入度不為1(就是能只通過這個人來獲取信息),那么我們就可以少一次詢問,則標記\(flag=1\)。
綜上,若\(flag==1,ans=1\)-\((縮點后入度為0的點\)-\(1)/n\).
若\(flag==0,ans=1\)-\((縮點后入度為0的點)/n\).
下面是代碼歐。。。
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cctype>
#define ll long long
#define R register
#define M 300005
#define N 100005
using namespace std;
template<typename T>inline void read(T &a){
    char c=getchar();T x=0,f=1;
    while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
    a=f*x;
}
int n,m,h[N],vis[N],low[N],dfn[N],num,tot,sta[N],top,cnt,col[N],siz[N];
int u[M],v[M],in[N],flag,ans;
struct node{
    int nex,to;
}edge[M];
inline void add(R int u,R int v){
    edge[++tot].nex=h[u];
    edge[tot].to=v;
    h[u]=tot;
}
inline void tarjan(R int x){
    dfn[x]=low[x]=++num;
    sta[++top]=x;vis[x]=1;
    for(R int i=h[x];i;i=edge[i].nex){
        R int xx=edge[i].to;
        if(!dfn[xx]){
            tarjan(xx);
            low[x]=min(low[x],low[xx]);
        }
        else if(vis[xx])low[x]=min(low[x],dfn[xx]);
    }
    if(low[x]==dfn[x]){
        R int now=-1;cnt++;
        while(now!=x){
            now=sta[top];
            top--;
            vis[now]=0;
            siz[cnt]++;
            col[now]=cnt;
        }
    }
}
int main(){
    read(n);read(m);
    for(R int i=1;i<=m;i++)
        read(u[i]),read(v[i]),add(u[i],v[i]);
    for(R int i=1;i<=n;i++)
        if(!dfn[i])tarjan(i);
    memset(h,0,sizeof(h));tot=0;
    for(R int i=1;i<=m;i++)
        if(col[u[i]]!=col[v[i]]){
            in[col[v[i]]]++;
            add(col[u[i]],col[v[i]]);//縮點后重新建圖
        }
    for(R int i=1;i<=cnt;i++){
        if(!flag&&!in[i]&&siz[i]==1){//找上述符合條件的點
        	R int pd=0;
            for(R int j=h[i];j;j=edge[j].nex){
                R int xx=edge[j].to;
                if(in[xx]==1)pd=1;
            }
            if(!pd)flag=1;
        }
        if(!in[i])ans++;
    }
    if(flag)ans--;//若標記則可以少一次詢問
    printf("%.6f\n",1.0-(double)ans/(double)n);
    return 0;
}
 
         
         
       