[中山市选] 杀人游戏 题解

前言

题目链接:洛谷Hydro & bzoj

题意简述

警察希望能在 \(n\) 个人里面,查出谁是杀手。警察能够对每一个人进行查证,假如查证的对象是平民,他会告诉警察,他认识的人,谁是杀手,谁是平民。假如查证的对象是杀手,杀手将会把警察干掉。现在警察掌握了每一个人认识谁。每一个人都有可能是杀手,可看作他们是杀手的概率是相同的。

问:根据最优的情况,保证警察自身安全并知道谁是杀手的概率最大是多少?

题目分析

发现,我们如果冒着风险,查验了某一个人,那这个人能够到达的所有点都可以无伤访问。问题就成了在 \(n\) 个点内,选择最少的点,使得这些点的后代能够覆盖所有点。

原图没什么性质,考虑 tarjan 缩成一个 DAG。那么选择了一个点,就可以覆盖 DAG 上,它所在连通块的所有后继。显然,每次选择入度为 \(0\) 的点,是最优的;同时,选择所有入度为 \(0\) 的点,又是必须的。

设缩点后,入度为 \(0\) 的点有 \(yzh\) 个,我们所求的不死的概率就是 \(\cfrac{n - yzh}{n}\)。考虑一个点,落在 \(n - yzh\) 个点中的概率。

此外,如果有一个点,它没有被访问,但是我们知道其它 \(n - 1\) 个点都是平民,我们可以推断出它一定是杀手。也就是说,如果 DAG 上有一个大小为 \(1\) 的入度为 \(0\) 的点,并且它指向的点都能被其它入度为 \(0\) 的点访问,它就可以留到最后。即,指向的点的入度都大于 \(1\)。应该注意的是,这样的点最多只能有一个。所求概率是 \(\cfrac{n - yzh - 1}{n}\)

时间复杂度线性 \(\Theta(n + m)\)

代码

// #pragma GCC optimize(3)
// #pragma GCC optimize("Ofast", "inline", "-ffast-math")
// #pragma GCC target("avx", "sse2", "sse3", "sse4", "mmx")
#include <iostream>
#include <cstdio>
#define debug(a) cerr << "Line: " << __LINE__ << " " << #a << endl
#define print(a) cerr << #a << "=" << (a) << endl
#define file(a) freopen(#a".in", "r", stdin), freopen(#a".out", "w", stdout)
#define main Main(); signed main() { return ios::sync_with_stdio(0), cin.tie(0), Main(); } signed Main
using namespace std;

int n, m;

struct Graph{
	struct node{
		int to, nxt;
	} edge[500010 << 1];
	int eid, head[500010];
	inline void add(int u, int v){
		edge[++eid] = {v, head[u]};
		head[u] = eid;
	}
	inline node & operator [] (const int x){
		return edge[x];
	}
} xym, yzh;

int dfn[100010], low[100010], timer;
int sccno[100010], scc_cnt;
int stack[100010], top;
int siz[100010];
bool in_stack[100010];

void tarjan(int now) {
    dfn[now] = low[now] = ++timer, in_stack[stack[++top] = now] = true;
    for (int i = xym.head[now]; i; i = xym[i].nxt) {
        int to = xym[i].to;
        if (dfn[to] == 0) tarjan(to), low[now] = min(low[now], low[to]);
        else if (in_stack[to]) low[now] = min(low[now], dfn[to]);
    }
    if (low[now] == dfn[now]){
        ++scc_cnt;
        do {
        	int now = stack[top--];
        	in_stack[now] = false;
        	sccno[now] = scc_cnt;
			++siz[scc_cnt];
		} while (stack[top + 1] != now);
    }
}

int du[100010];

signed main() {
	scanf("%d%d", &n, &m);
	for (int i = 1, u, v; i <= m; ++i) {
		scanf("%d%d", &u, &v);
		xym.add(u, v);
	}
	for (int i = 1; i <= n; ++i) if (!dfn[i]) tarjan(i);
	for (int i = 1; i <= n; ++i) {
		static bool vis[100010];
		for (int j = xym.head[i], to; to = xym[j].to, j; j = xym[j].nxt) {
			if (sccno[i] != sccno[to]) {
				if (vis[sccno[to]]) continue;
				vis[sccno[to]] = true;
				++du[sccno[to]];
				yzh.add(sccno[i], sccno[to]);
			}
		}
		for (int j = xym.head[i], to; to = xym[j].to, j; j = xym[j].nxt)
			vis[sccno[to]] = false;
	}
	int ans = 0;
	for (int i = 1; i <= scc_cnt; ++i) ans += !du[i];
	for (int i = 1; i <= scc_cnt; ++i) if (du[i] == 0 && siz[i] == 1) {
		bool flag = true;
		for (int j = yzh.head[i], to; to = yzh[j].to, j; j = yzh[j].nxt) {
			if (du[to] == 1) {
				flag = false;
				break;
			}
		}
		if (flag) {
			--ans;
			break;
		}
	}
	printf("%.6lf", 1 - 1.0 * ans / n + 1e-10);
	return 0;
}

后记 & 反思

如果是图论,并且给出的图并没有什么性质,考虑缩点。这题那个贪心思想也是后面顺理成章的思路的关键。

posted @ 2024-07-13 16:07  XuYueming  阅读(3)  评论(0编辑  收藏  举报