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

题目链接

题目大意:有n个人,其中一个是杀手,可以询问一些人,如果是杀手就会死,如果是平民,他会告诉你他认识的人中有谁是杀手有谁是平民,求自身安全并知道杀手的概率最大是多少

题解:某个人是杀手的概率相等。考虑到每个SCC只问一个点就可以,进行缩点,缩点后每个SCC对答案贡献等价。
知道谁是杀手相当于知道所有人的身份,需要在DAG中选择x个点,使得至少能够遍历n-1个点(确定n-1就可以确定全部),要求x最小,那么显然x取所有入度为0的点,答案即为 (nx)÷n
如果某个SCC满足入度为0的条件,但是其大小为1,且这个SCC的所有出边到达的点入度均大于1,那么这个点就可以不选(即上文中的n-1情况),图中只能有1个这样的点

我的收获:朴素算概率,讨论

#include <iostream>
#include <cstdio>
#include <cstring>
#include <iomanip>
#include <algorithm>
using namespace std;

#define M 100005

int tm,scnt,top;
int n,m,t,t2,ans;
int head[M],last[M];
int low[M],dfn[M],col[M],sz[M],in[M],s[M*5];
bool ins[M],mark[M];

struct edge{int to,nex;}e[M*5],p[M*5];

void add(int u,int v){e[t].to=v,e[t].nex=head[u],head[u]=t++;}
void insert(int u,int v){p[t2].to=v,p[t2].nex=last[u],last[u]=t2++;}

void tarjan(int x){
    int now=0;
    dfn[x]=low[x]=++tm;
    s[++top]=x;ins[x]=true;
    for(int i=head[x];i!=-1;i=e[i].nex){
        int v=e[i].to;
        if(!dfn[v]) tarjan(v),low[x]=min(low[x],low[v]);
        else if(ins[v]&&dfn[v]<low[x]) low[x]=dfn[v];
    }
    if(low[x]==dfn[x]){
        scnt++;
        while(x!=now) now=s[top--],ins[now]=false,col[now]=scnt,sz[scnt]++; 
    }
}

void rebuild(){
    for(int x=1;x<=n;x++){
        for(int i=head[x];i!=-1;i=e[i].nex){
            int v=e[i].to;
            if(col[x]!=col[v]&&!mark[col[v]])
                mark[col[v]]=1,in[col[v]]++,insert(col[x],col[v]);
        }
        for(int i=head[x];i!=-1;i=e[i].nex)
        if(col[x]!=col[e[i].to]) mark[col[e[i].to]]=0;
    }
}

bool check(int x){  
    for(int i=last[x];i!=-1;i=p[i].nex) if(in[p[i].to]==1) return false;  
    return true;
}

void work()
{
    for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
    rebuild();
    for(int i=1;i<=scnt;i++) if(!in[i]) ans++;
    for(int x=1;x<=scnt;x++) if(sz[x]==1&&!in[x]&&check(x)) {ans--;break;}
    cout<<fixed<<setprecision(6)<<1.0-(double)ans/n<<endl;
}

void init()
{
    int x,y;t=0;memset(head,-1,sizeof(head));
    t2=0;memset(last,-1,sizeof(last));
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++) scanf("%d%d",&x,&y),add(x,y);
}

int main() 
{
    init();
    work();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值