2438: [中山市选2011]杀人游戏 tarjan+概率与期望

本文提出了一种高效算法,旨在解决在包含环形结构和强连通分量的复杂网络中,确定杀手身份的问题。通过利用图论中的Tarjan算法进行环的压缩和强连通分量的识别,进一步优化询问策略,本文算法能够准确并高效地识别出杀手身份,同时减少不必要的询问次数。特别关注于处理入度为零且大小为1的强连通分量的特殊情况,以避免冗余询问,从而提高整体解决方案的效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这是一道好题啊。
首先想这样一个问题,如果存在一个环,我们只需要问其中一个人,若它不是杀手,那么我们便知道了环内所有人的身份(根据最优的情况),因此可以先tarjan缩一下环。
然后,对于每一个入度为0的强连通分量,我们都是要去询问的。所以ans=入度为0的强连通分量的个数。
存在一种特殊情况:入度为零,大小为1的强连通分量,且这个单点的所有出边指向的点所在的强连通分量 入度都大于等于2,及它指向的所有点都能被其他点访问到。这时我们便无需访问这个点,因为通过访问其他点我们就可以知道其他n-1个人的身份,而剩下一个排除就好了。

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 100005
using namespace std;
int n,m,scc,cnt,cnt0,ans,top,tot;
int head[N],head0[N],ind[N];
int low[N],dfn[N],stack[N],belong[N],num[N];
bool inset[N],v[N];
int next[300005],next0[300005],list[300005],list0[300005];
inline int read()
{
    int a=0,f=1; char c=getchar();
    while (c<'0'||c>'9') {if (c=='-') f=-1; c=getchar();}
    while (c>='0'&&c<='9') {a=a*10+c-'0'; c=getchar();}
    return a*f;
}
inline void insert(int x,int y)
{
    next[++cnt]=head[x];
    head[x]=cnt;
    list[cnt]=y;
}
inline void insert0(int x,int y)
{
    next0[++cnt0]=head0[x];
    head0[x]=cnt0;
    list0[cnt0]=y;
    ind[y]++;
}
void dfs(int x)
{
    dfn[x]=low[x]=++tot;
    inset[x]=1;
    stack[++top]=x;
    for (int i=head[x];i;i=next[i])
        if (!dfn[list[i]])
        {
            dfs(list[i]);
            low[x]=min(low[x],low[list[i]]);
        }
        else if (inset[list[i]]) low[x]=min(low[x],dfn[list[i]]);
    if (dfn[x]==low[x])
    {
        int i=-1; scc++;
        while (i!=x)
        {
            i=stack[top--];
            belong[i]=scc;
            inset[i]=0;
            num[scc]++;
        }
    }
}
inline void tarjan()
{
    for (int i=1;i<=n;i++) if (!dfn[i]) dfs(i);
}
inline void rebuild()
{
    for (int x=1;x<=n;x++)
    {
        for (int i=head[x];i;i=next[i])
            if (belong[x]!=belong[list[i]]&&!v[belong[list[i]]])
                v[belong[list[i]]]=1,insert0(belong[x],belong[list[i]]);
        for (int i=head[x];i;i=next[i])
            if (belong[x]!=belong[list[i]]) v[belong[list[i]]]=0;
    }
}
inline bool judge(int x)
{
    if (ind[x]!=0||num[x]!=1) return 0;
    for (int i=head0[x];i;i=next0[i])
        if (ind[list0[i]]==1) return 0;
    return 1;
}
int main()
{
    n=read(); m=read();
    for (int i=1;i<=m;i++)
    {
        int u=read(),v=read();
        insert(u,v);
    }
    tarjan();
    rebuild();
    for (int i=1;i<=scc;i++) 
        if (!ind[i]) ans++;
    for (int i=1;i<=scc;i++) 
        if (judge(i)) {ans--; break;}
    printf("%.6lf",(double)(n-ans)/n);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值