P4819 [中山市选] Tarjan SCC

题意

传送门 P4819 [中山市选]杀人游戏

题解

x x x 认识 y y y,则从 x x x y y y 连一条有向边,得到一张有向图。观察发现,若存在环,那么任取环上一点即可,则有 1 / N 1/N 1/N 的概率是杀手;否则,可获取下一个节点的身份,直到环上最后一个节点或发现杀手。

那么使用 T a r j a n Tarjan Tarjan 算法求解 S C C SCC SCC,缩减建图,得到一个 D A G DAG DAG。对于入度为 0 0 0 的点 x x x,显然无法通过检查其他节点来获取 x x x 的身份,那么需要检查这个节点。对于入度大于 0 0 0 的节点 x x x,可以通过拓扑序小于它的节点 y y y 来获取 x x x 的身份。那么需要检查的节点为 s u m = ∑ x ∈ G [ i n d e g [ x ] = 0 ] sum=\sum\limits_{x\in G} \big[indeg[x]=0\big] sum=xG[indeg[x]=0]

但算法仍不完全正确,因为获取某个人的身份,除了检查、从认识其的人了解,还有第三种方法:排除法。若图中仅剩最后一个节点未了解其身份,那么必然可以通过排除法进行推断。那么需要判断入度为 0 0 0 的点中,是否存在可以运用排除法的节点。

首先,这样的节点对应的 S C C SCC SCC 中节点个数为 1 1 1,否则不能判断环上的其他节点的身份;其次,这样的节点 x x x 若存在出边 ( x , y ) (x,y) (x,y),则 y y y 一定有一条来自其他节点 z z z 的入边,否则 y y y 的身份无法了解,为了简单的判断是否存在这样的入边,在建立 D A G DAG DAG 时将节点间连边去重,即可用节点的入度表示节点可以被多少个不同的节点更新。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 100005, maxm = 300005;
int N, M, num, low[maxn], dfn[maxn];
int tot, head[maxn], to[maxm], nxt[maxm];
int tot_c, hc[maxn], tc[maxm], nc[maxm];
int top, st[maxn], scc, sc[maxn], in[maxn], sz[maxn];
bool ins[maxn], vs[maxn];

inline void add(int x, int y) {
    
     to[++tot] = y, nxt[tot] = head[x], head[x] = tot; }

inline void add_c(int x, int y) {
    
     tc[++tot_c] = y, nc[tot_c] = hc[x], hc[x] = tot_c; }

void tarjan(int x)
{
    
    
    low[x] = dfn[x] = ++num;
    st[++top] = x, ins[x] = 1;
    for (int i = head[x]; i; i = nxt[i])
    {
    
    
        int y = to[i];
        if (!dfn[y])
            tarjan(y), low[x] = min(low[x], low[y]);
        else if (ins[y])
            low[x] = min(low[x], dfn[y]);
    }
    if (low[x] == dfn[x])
    {
    
    
        ++scc;
        int y;
        do
        {
    
    
            y = st[top--], ins[y] = 0;
            sc[y] = scc, ++sz[scc];
        } while (y != x);
    }
}

int main()
{
    
    
    scanf("%d%d", &N, &M);
    for (int i = 1, x, y; i <= M; ++i)
        scanf("%d%d", &x, &y), add(x, y);
    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 x = sc[i], y = sc[to[j]];
            if (x != y && !vs[y])
                vs[y] = 1, add_c(x, y), ++in[y];
        }
        for (int j = head[i]; j; j = nxt[j])
            vs[sc[to[j]]] = 0;
    }
    int sum = 0, lst = 0;
    for (int i = 1; i <= scc; ++i)
        if (!in[i])
        {
    
    
            ++sum;
            if (!lst && sz[i] == 1)
            {
    
    
                bool f = 1;
                for (int j = hc[i]; j; j = nc[j])
                    f &= in[tc[j]] > 1;
                if (f)
                    --sum, lst = 1;
            }
        }
    printf("%.6f\n", (double)(N - sum) / N);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/neweryyy/article/details/115034377