【BZOJ2438】杀人游戏,tarjan缩点+简单的概率计算

Time:2016.05.21
Author:xiaoyimi
转载注明出处谢谢


传送门
思路:
首先把我难住的是怎么计算概率
一开始好像进入了思维误区,感觉计算这玩意非常困难,想了好久都没有想出来(题意理解不够透彻)
后来想明白,实际上警察就是两种结果——查到犯人或死亡,而死亡事件一定是包含在“调查未知身份的人”,也就是说调查未知身份的人越多,死亡概率就越高,所以我们要求的问题就是警察要尽可能少调查未知身份的人
给出公式
P=
“死亡”和”存活”是对立事件,而”存活”等价于”找到犯人”
所以
ans=1P
问题转化为求调查最少的未知身份人的人数
同时我们发现,对于一个环(1认识2,2认识3,3认识1),我们是可以把他们看作一个人的,也就是说,只要调查了他们其中一个,其他两个我们一定可以安全到达(知道他是犯人或平民)
对于这个问题,我们使用tarjan对图进行缩点处理,然后我们所要调查的对象就是重构图中那些入度为0的点(没有人知道这些点里的人的身份),调查的未知身份的人数就是这些点的数量
(如果其中入度不为0,那么我们一定可以从一个入度为0的点走到这里,即我们调查这个点时是知道这里面的人的身份的)
注意:
有一个特殊情况,如果说至少有一个点,它只有一个人,他是完全孤立的(即没有人知道这个人的身份,他对外面的世界也一无所知)或者他认识的每个人都被除他以外的其他人所认识(即它所能到达的点的入度>1),那么我们在安全调查完除他以外的其他人后,他一定是杀手(但在图上他属于未知身份的人),我们不必去调查他,直接抓起来就可以了,所以调查未知身份的人数要–
(我们可以想得简单些,比如n=1时这个人一定是杀手,即 P=1
代码:

#include<bits/stdc++.h>
#define M 100003
using namespace std;
int in()
{
    int t=0;char ch=getchar();
    while (!isdigit(ch)) ch=getchar();
    while (isdigit(ch)) t=(t<<1)+(t<<3)+ch-48,ch=getchar();
    return t;
}
stack<int>S;
int n=in(),m=in(),tot,cnt;
int dfn[M],low[M],first[M],First[M],belong[M],jin[M],siz[M];
bool vis[M];
struct edge{int v,next;}e[M*3],E[M*3];
void add(int y,int x){e[++tot]=(edge){y,first[x]};first[x]=tot;}
void Add(int x,int y){E[++tot]=(edge){y,First[x]};First[x]=tot;}
void dfs(int x)
{
    low[x]=dfn[x]=++cnt;
    vis[x]=1;
    S.push(x);
    for (int i=first[x];i;i=e[i].next)
        if (!dfn[e[i].v])
            dfs(e[i].v),
            low[x]=min(low[e[i].v],low[x]);
        else if (vis[e[i].v])
            low[x]=min(low[x],dfn[e[i].v]);
    if (dfn[x]==low[x])
    {
        siz[x]=0;
        for (int y=-1;y!=x;y=S.top(),S.pop())
            belong[S.top()]=x,
            siz[x]++,
            vis[S.top()]=0;
    }
}
main()
{
    for (int i=1;i<=m;i++) add(in(),in());
    tot=0;
    for (int i=1;i<=n;i++)
        if (!dfn[i]) dfs(i); 
    for (int i=1;i<=n;i++)
        for (int j=first[i];j;j=e[j].next)
            if (belong[i]!=belong[e[j].v])
                Add(belong[i],belong[e[j].v]),
                jin[belong[e[j].v]]++;
    int ans=0;
    for (int i=1;i<=n;i++)
        if (!vis[belong[i]]&&!jin[belong[i]])
            vis[belong[i]]=1,
            ans++;
    for (int i=1;i<=n;i++)
    if (siz[belong[i]]==1&&vis[belong[i]])
    {
        bool flag=0;
        for (int j=First[belong[i]];j;j=E[j].next)
            if (jin[E[j].v]<=1) {flag=1;break;}
        if (!flag) {ans--;break;}
    }
    printf("%.6lf",1.0-1.0*ans/n);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值