洛谷

洛谷题单指南-连通性问题-P4819 [中山市选] 杀人游戏

原题链接:https://www.luogu.com.cn/problem/P4819

题意解读:在一个有向图中,选择最少的点,使得可以判断所有点是否是杀手,设最少的点数量为x,结果是1 - x/n。

解题思路:

显然,可以先进行缩点。

缩点后会出现三种情况:

1、不存在孤立点也不存在多余点

统计所有入度为0的缩点个数x,从每个缩点中选一个即可,答案为1 - x / n。

2、不存在孤立点且存在多余点

图中,从1可以走遍1、2、3、4,如果确定了1、2、3、4的情况,5就不言自明,这样5就是多余的点,这样的点的特点是:入度为0且所有指向的节点的入度都大于1。

注意5在缩点之前节点数也是1个。

这时答案为1 - (x - 1) / n。

3、存在孤立点

对于其中一个孤立点5,也是多余的,因为其他的点如果都判断过,孤立点的结果自然知晓(其他点中如果找到杀手,孤立点必然不是杀手;其他点中如果未找到杀手,孤立点中必然有杀手)。

注意5在缩点之前节点数也是1个。

这时答案为1 - (x - 1) / n。

100分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 100005, M = 300005;
int head[N], to[M], nxt[M], idx; //原图
vector<int> g[N]; //缩点图
int dfn[N], low[N], timestamp;
stack<int> stk;
bool vis[N];
int scc[N], sz[N], cnt;
int in[N], out[N]; //缩点后入度、出度
int n, m;
map<pair<int, int>, int> mp;

void add(int a, int b)
{
    to[++idx] = b;
    nxt[idx] = head[a];
    head[a] = idx;
}

void tarjan(int u)
{
    dfn[u] = low[u] = ++timestamp;
    stk.push(u);
    vis[u] = true;
    for(int i = head[u]; i; i = nxt[i])
    {
        int v = to[i];
        if(!dfn[v])
        {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        }
        else if(vis[v]) low[u] = min(low[u], dfn[v]);
    }
    if(low[u] == dfn[u])
    {
        cnt++;
        while(true)
        {
            int t = stk.top(); stk.pop();
            vis[t] = false;
            scc[t] = cnt;
            sz[cnt]++;
            if(t == u) break;
        }
    }
}

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= m; i++)
    {
        int u, v;
        cin >> u >> v;
        add(u, v);
    }

    for(int i = 1; i <= n; i++)
        if(!dfn[i])
            tarjan(i);

    for(int u = 1; u <= n; u++)
    {
        for(int i = head[u]; i; i = nxt[i])
        {
            int v = to[i];
            int a = scc[u], b = scc[v];
            if(a != b)
            {
                if(mp.count({a, b})) continue; //判重边
                mp[{a, b}] = 1;
                out[a]++;
                in[b]++;
                g[a].push_back(b);
            }
        }
    }

    bool flag = false;
    int x = 0;
    for(int u = 1; u <= cnt; u++)
    {
        if(!in[u]) 
        {
            x++;
            if(sz[u] == 1)
            {
                if(!out[u]) flag = true; //孤立点
                else
                {
                    int ok = true;
                    for(auto v : g[u])
                    {
                        if(in[v] <= 1) 
                        {
                            ok = false; //只有有一个邻点的入度小于等于1
                            break;
                        }
                    }
                    if(ok) flag = true;
                }
            }   
        }
    }

    if(flag) printf("%.6f", 1 - 1.0 * (x - 1) / n);
    else printf("%.6f", 1 - 1.0 * x / n);

    return 0;
}