题解 P4819 【[中山市选]杀人游戏】

· · 题解

这题看起来是很恶心的概率题。
不过想清楚其实也没有那么难。
下面根据一些样例图来讲几种情况。

这是样例的图。根据题目,每个人是杀手的概率是相同的。在这里是1/5,即0.2。容易发现,如果调查第一个人(最优情况),而且这个人不是杀手,就能知道所有人的信息。而第一个人是杀手的概率为0.2,成功的概率就是1-0.2=0.8。

同样的,这里也是从1开始调查。如果1是平民,那么1,2,3,4的情况就全部清楚了。因为1是平民时,可以获知2的情况,如果2是杀手,那么不需要再调查了。如果2是平民,2会提供3的信息,以此类推。

换句话说,从甲开始调查所能了解到情况的人,就是从图上甲对应的点出发,可以遍历到的所有点
转化
从第二张图我们可以发现,点集(2,3,4)中,只要了解了其中一个点,其他点的情况也清楚了。这就是一个强联通分量。我们可以用Tarjan算法把这样的所有点集缩成一个点,方便后面的处理。关于Tarjan算法,这里就不仔细解说了。可以参照一下蒟蒻写的另外一篇博客。
缩点后,上面的第二张图就转化成这样。

这样问题就转化为了求有多少个入度为0的点(缩点后必定有入度为0的点,而从这些点出发,必定最优)。有多少个点,就要调查多少次。生命受到威胁的概率就是

(1/n)*num

但是
再看转化后的第二张图。调查1后,1和(2,3,4)的情况都明白了。这时候,杀手如果在1,2,3,4中,不用继续调查了。如果不在1,2,3,4中,那么5必定是杀手,也不用继续调查!这个样例的答案是0.8。

可以不调查最后一个点的情况 这里先说明,如果一个点,在缩点前也是一个点,指向的点的入度都大于1,且入度为0,那么这个点在最后可以不调查。下面给出证明: 1.入度为0。只有这样才能最优。

例如在这个图中,如果从入度不为0的2开始调查,那么指向2的1必定不能调查到。因此不是最优的。

2.指向的点入度大于1。
在第二张图缩点后的图的基础上,如果增加一个6指向5。这时1和6都是入度为0。但此时从1开始后,只能弄清楚1和(2,3,4)的情况,5的情况是不清楚的,这时候如果不调查6,不能确定的点有两个。

3.缩点前仍然是一个点 缩点后,这个图看起来可以从5调查起,然后5和4的情况都清楚了,剩下一个(1,2,3)。但是,再看看缩点前的图。

其实还剩下3个点,还需要从其中一个再调查一次。
4.此外补充一点,如果一个点是出度为0,入度也为0,那么这个点如果放在最后,也可也不调查。

注意 这里重构图的时候,一定要去重边,否则会对出度和入度的判断造成影响。
这里感谢一下某大佬教蒟蒻用map去重边。

丑代码和注释

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <cctype>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <map>
#include <cstdlib>
#include <ctime>

using namespace std;

int n,m,i,j,k,l,dfn[100010],low[100010],scc[100010],ti,sc,pnum[100010],ind[100010],need,outd[100010];
bool p;
double per;

vector<int>to[100010];
vector<int>newto[100010];
stack<int>s;
map<int,int>edge[100010];

void Tarjan(int now)
{
    int i,j;
    dfn[now]=++ti;
    low[now]=dfn[now];
    s.push(now);
    for(i=0;i<to[now].size();i++)
    {
        int next=to[now][i];
        if(dfn[next]==-1)
        {
            Tarjan(next);
            low[now]=min(low[now],low[next]);
        }
        else
        if(scc[next]==-1)
        {
            low[now]=min(low[now],dfn[next]);
        }
    }
    if(dfn[now]==low[now])
    {
        int snow;
        sc++;
        while(true)
        {
            snow=s.top();
            s.pop();
            scc[snow]=sc;
            if(snow==now)break;
        }
    }
}

void Reconstruction()
{
    int i,j;
    for(i=1;i<=n;i++)
    {
        for(j=0;j<to[i].size();j++)
        if(scc[i]!=scc[to[i][j]]&&!edge[scc[i]][scc[to[i][j]]])//判自环和map判重边
        {
            newto[scc[i]].push_back(scc[to[i][j]]);
            edge[scc[i]][scc[to[i][j]]]=1;
            ind[scc[to[i][j]]]++;
            outd[scc[i]]++;
        }
        pnum[scc[i]]++;
    }
}

int main()
{
    freopen("kill.in","r",stdin);
    freopen("kill.out","w",stdout); 
    scanf("%d%d",&n,&m);
    for(i=1;i<=m;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        to[u].push_back(v);
    }
    for(i=1;i<=n;i++){dfn[i]=-1;scc[i]=-1;}
    for(i=1;i<=n;i++)
    {
        if(dfn[i]==-1)
        {
            Tarjan(i);
        }
    }
    Reconstruction();//重构图
    p=0;
    for(i=1;i<=sc;i++)
    {
        if(ind[i]==0)
        {
            need++;//需要调查的人加一
            if(pnum[i]==1)//如果缩点前是1个点
            {
                if(outd[i]==0)p=1;//入度为0出度为0,特判
                else
                {
                    bool p2=1;
                    for(j=0;j<newto[i].size();j++)
                    {
                        int next=newto[i][j];
                        if(ind[newto[i][j]]<=1)
                        {
                            p2=0;
                            break;
                        }
                    }
                    //检查是否所有指向的点的入度都大于1
                    if(p2)p=1;
                }
            }
        }
    }
    if(p)need--;//最后一个不查
    per=1.0/(n*1.0);;
    printf("%.6lf",1-per*need);
    return 0;
}

蒟蒻写文,如有不当,请不留情面地打脸!