【BZOJ-2438】杀人游戏 Tarjan + 缩点 + 概率

2438: [中山市选2011]杀人游戏

Time Limit: 10 Sec  Memory Limit: 128 MB
Submit: 1638  Solved: 433
[Submit][Status][Discuss]

Description

一位冷血的杀手潜入 Na-wiat,并假装成平民。警察希望能在 N 个人里面,查出谁是杀手。
警察能够对每一个人进行查证,假如查证的对象是平民,他会告诉警察,他认识的人, 谁是杀手, 谁是平民。 假如查证的对象是杀手, 杀手将会把警察干掉。
现在警察掌握了每一个人认识谁。
每一个人都有可能是杀手,可看作他们是杀手的概率是相同的。
问:根据最优的情况,保证警察自身安全并知道谁是杀手的概率最大是多少?

Input

第一行有两个整数 N,M。
接下来有 M 行,每行两个整数 x,y,表示 x 认识 y(y 不一定认识 x,例如 * * * 同志) 。

Output

仅包含一行一个实数,保留小数点后面 6 位,表示最大概率。

Sample Input

5 4
1 2
1 3
1 4
1 5

Sample Output

0.800000

HINT

警察只需要查证 1。假如1是杀手,警察就会被杀。假如 1不是杀手,他会告诉警
察 2,3,4,5 谁是杀手。而 1 是杀手的概率是 0.2,所以能知道谁是杀手但没被杀的概
率是0.8。对于 100%的数据有 1≤N ≤  10 0000,0≤M ≤  30 0000

数据已加强!

Source

Solution

不错的题,思路应该比较简单,但容易遗漏问题

把认识关系转化到图上,那么我们发现,如果我们询问一个平民,那么他的所有后继点就都知道了

那么我们先对图进行Tarjan缩一下点,有入度的点,显然我们可以不用直接访问,那么我们访问每个入度为0的点

不过这里有个特殊情况,如果存在一个被搁置的点,他最后是不用访问的比如:3个人ABC,A认识B,那么访问A后,A,B和C的身份都能得知

这样就可以少询问一个,但是注意,这种情况的条件是:

入度为0,且只包含1个点,且这个点指向的SCC的入度>=2(缩点前)【并不仅仅是出入度为0】<-特别容易出错

比如:3个人ABC,A认识B,C认识B,那么访问A或C后都可以得到所有人身份;

证明:

若这个点的所有出边所指向的强连通分量都有其它的前驱 那么我把这个点放在最后 用作排除不会对推理造成干扰 反之若有一个后继入度为1 那么就算不调查这个单点也要调查那个后继 对答案没有影响

然后答案显然是(N-x)/N (x为需要询问的点数)

Code

#include<iostream>  
#include<cstdio>  
#include<cstring>  
#include<algorithm>  
#include<cmath>  
#include<map>  
using namespace std;  
int read()  
{  
    int x=0,f=1; char ch=getchar();  
    while (ch<'0' || ch>'9') {if (ch=='-') f=-1; ch=getchar();}  
    while (ch>='0' && ch<='9') {x=x*10+ch-'0'; ch=getchar();}  
    return x*f;  
}  
#define MAXN 100010  
#define MAXM 300010 
int N,M;  
struct EdgeNode{int next,to;}edge[MAXM],road[MAXM];  
int cnt,tot,head[MAXN],last[MAXN];  
void AddEdge(int u,int v) {cnt++; edge[cnt].next=head[u]; head[u]=cnt; edge[cnt].to=v;}  
void AddRoad(int u,int v) {tot++; road[tot].next=last[u]; last[u]=tot; road[tot].to=v;}  
int dfn[MAXN],low[MAXN],scc,t,belong[MAXN],visit[MAXN],size[MAXN],ind[MAXN],ans,st[MAXN],top;  
void Tarjan(int x)  
{  
    dfn[x]=low[x]=++t;  
    visit[x]=1; st[++top]=x;  
    for (int i=head[x]; i; i=edge[i].next)  
        if (!dfn[edge[i].to])  
            Tarjan(edge[i].to),low[x]=min(low[x],low[edge[i].to]);  
        else 
            if (visit[edge[i].to]) low[x]=min(low[x],dfn[edge[i].to]);  
    if (dfn[x]==low[x])  
        {  
            scc++; int now=0; 
            while (x!=now)  
                now=st[top--],size[scc]++, 
                visit[now]=0,belong[now]=scc;  
        }  
}  
map<int,bool>mp; 
bool check(int x)  
{  
    if (ind[x]!=0 || size[x]!=1) return 0;  
    for (int i=last[x]; i; i=road[i].next)  
        if (ind[road[i].to]==1) return 0;  
    return 1;  
}  
int main()  
{  
    N=read(),M=read();  
    int x,y;  
    while (M--) x=read(),y=read(),AddEdge(x,y);  
    for (int i=1; i<=N; i++) if (!dfn[i]) Tarjan(i);  
    for (int i=1; i<=N; i++)  
        {  
            mp.clear(); 
            for (int j=head[i]; j; j=edge[j].next)  
                if (belong[i]!=belong[edge[j].to] && !mp[belong[edge[j].to]])   
                    ind[belong[edge[j].to]]++,AddRoad(belong[i],belong[edge[j].to]),mp[belong[edge[j].to]]=1;  
        }  
    for (int i=1; i<=scc; i++) if (!ind[i]) ans++;  
    for (int i=1; i<=scc; i++)  
        if (check(i)) {ans--;break;}  
    printf("%.6lf",double(N-ans)/N);  
    return 0;  
}

这题一眼秒思路,然后细节WA了好久...最后看了Po姐才了解到问题

细节啊细节!!

posted @ 2016-07-28 15:56  DaD3zZ  阅读(657)  评论(0编辑  收藏  举报