bzoj2438 luogu4819 [中山市选]杀人游戏

https://www.lydsy.com/JudgeOnline/problem.php?id=2438

一开始以为是一道大水题,后来发现有些地方还是要想清楚才能写出来

首先Tarjan缩点

在不清楚的情况下肯定不选入度大于等于1的SCC询问,因为他们中有没有杀手可以通过其前驱知道,没必要冒险

而稍微推一下就知道,每次选择一个入度为0的SCC询问的风险是$\frac{1}{n}$

所以就统计有多少个入度为0的SCC就行了?

不,有一种特殊情况,假如已经调查了$n-1$个人,剩下一个人肯定是杀手

所以我们尽量使它出现这样的情况,$f_i$表示到达某个SCC的方案数

如果对于某个只含一个节点且入度为0的SCC,它的后继节点全都有至少两种方式到达,那么我们可以把它最为剩下的一个人

 

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,M=3*(1e5+5);
int n,m;
int head[N],ver[M],nxt[M],ce;
int dfn[N],cnt,low[N],c[N],cd,st[N],top;
int deg[N],sz[N],f[N];
vector<int> to[N];
bool ins[N],mk[N];
int ans;
void tarjan(int now)
{
    dfn[now]=low[now]=++cnt,st[++top]=now,ins[now]=1;
    for(int i=head[now];i;i=nxt[i])
    {
        int y=ver[i];
        if(!dfn[y])
        {
            tarjan(y); low[now]=min(low[now],low[y]);
        }
        else if(ins[y]) low[now]=min(low[now],dfn[y]);
    }
    if(dfn[now]==low[now])
    {
        int y=-1; ++cd;
        while(y!=now)
            y=st[top--],ins[y]=0,c[y]=cd,++sz[cd];
    }
}
void adde(int a,int b)
{
    ver[++ce]=b,nxt[ce]=head[a],head[a]=ce;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;++i)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        adde(u,v);
    }
    for(int i=1;i<=n;++i)
        if(!dfn[i]) tarjan(i);
    for(int i=1;i<=n;++i)
        for(int j=head[i];j;j=nxt[j])
        {
            int y=ver[j];
            if(c[y]!=c[i])
            {
                ++deg[c[y]];
                to[c[i]].push_back(c[y]);
            }
        }
    queue<int> q;
    for(int i=1;i<=cd;++i)
        if(!deg[i]) {q.push(i);++ans,f[i]=mk[i]=1;}
    while(!q.empty())
    {
        int u=q.front(); q.pop();
        for(int i=0;i<to[u].size();++i)
        {
            int v=to[u][i];
            f[v]+=f[u],--deg[v];
            if(!deg[v]) q.push(v);
        }
    }
    bool flag=0;
    for(int i=1;i<=cd;++i)
        if(mk[i] && sz[i]==1)
        {
            flag=1;
            for(int j=0;j<to[i].size();++j)
                if(f[to[i][j]]==1) {flag=0; break;}
            if(flag) break;
        }
    if(flag) --ans;
    ans=n-ans;
    printf("%.6lf",(double)(ans)/(double)(n));
    return 0;
}

 

转载于:https://www.cnblogs.com/w19567/p/11321514.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值