只要你跑的够快,锅就追不上你

0%

「BZOJ 2438」杀人游戏(Tarjan + 概率论)

题目大意

「BZOJ 2438」杀人游戏

给定一个 $n$ 个点 $m$ 条边的有向无向图,每个点代表一个人,每条单向边代表一组认识关系。已知有一个人是杀手,每个人是杀手的概率都是 $\frac{1}{n}$。有一个警察,每次可以询问一个人,他如果是杀手就会把警察杀掉,如果不是的话他会告诉警察他认识的人中谁是杀手或没有杀手。问警察选择最优策略,查到杀手后还存活的概率。

数据范围:$n \le 10^5, m \le 3 \times 10^5$。

思路分析

首先发现答案等于 $(n - k) / n$,其中 $k$ 表示不知道某人的身份就去查他的次数。然后发现在同一个 SCC 中,我们只需要查一个人即可,并且在缩点后的图中,我们查完一个点就可以查所有它能到达的点。于是,我们统计缩点后的 DAG 度数为 $0$ 的点数即可。

但是有一种特殊情况。因为杀手只有一个,所以在某些情况下我们不需要查完所有点。如果我们查完了 $n - 1$ 个点都是平民,那么剩下的一个点就肯定是杀手。于是我们特判一下,如果缩点后存在一个度数为 $0$ 的点在原图上对应的点数为 $1$,并且它的所有出点的入度都大于 $1$,那么我们就可以不查这个点,也就是将 $k$ 减去一。注意这种操作只能做一次。

这样,我们就解决了这个问题。时间复杂度 $O(n + m)$。这道题告诉我们,一些关于连通性的图论问题往往需要考虑一些特殊情况。

代码实现

1
#include <cstdio>
2
#include <cstdlib>
3
#include <vector>
4
using namespace std;
5
6
const int maxn = 1e5, maxm = 3e5;
7
int n, m, u[maxm + 3], v[maxm + 3], tm, dfn[maxn + 3], low[maxn + 3];
8
int top, st[maxn + 3], cnt, sum[maxn + 3], bel[maxn + 3], deg[maxn + 3];
9
bool in[maxn + 3], ok[maxn + 3];
10
vector<int> G[maxn + 3];
11
12
void add(int u, int v) {
13
	G[u].push_back(v);
14
}
15
16
void tarjan(int u) {
17
	dfn[u] = low[u] = ++tm;
18
	st[++top] = u, in[u] = true;
19
	for (int i = 0, v; i < G[u].size(); i++) {
20
		v = G[u][i];
21
		if (!dfn[v]) {
22
			tarjan(v);
23
			low[u] = min(low[u], low[v]);
24
		} else if (in[v]) {
25
			low[u] = min(low[u], dfn[v]);
26
		}
27
	}
28
	if (dfn[u] == low[u]) {
29
		++cnt;
30
		do {
31
			sum[cnt]++;
32
			bel[st[top]] = cnt;
33
			in[st[top]] = false;
34
		} while (st[top--] != u);
35
	}
36
}
37
38
int main() {
39
	scanf("%d %d", &n, &m);
40
	for (int i = 1; i <= m; i++) {
41
		scanf("%d %d", &u[i], &v[i]);
42
		add(u[i], v[i]);
43
	}
44
	for (int i = 1; i <= n; i++) {
45
		if (!dfn[i]) {
46
			tarjan(i);
47
		}
48
	}
49
	for (int i = 1; i <= n; i++) {
50
		G[i].clear();
51
	}
52
	for (int i = 1; i <= m; i++) {
53
		if (bel[u[i]] != bel[v[i]]) {
54
			deg[bel[v[i]]]++;
55
			G[bel[u[i]]].push_back(bel[v[i]]);
56
		}
57
	}
58
	int ans = 0;
59
	for (int i = 1; i <= cnt; i++) {
60
		ans += !deg[i];
61
	}
62
	for (int i = 1; i <= cnt; i++) {
63
		if (!deg[i] && sum[i] == 1) {
64
			bool flag = true;
65
			for (int j = 0; j < G[i].size(); j++) {
66
				if (deg[G[i][j]] == 1) {
67
					flag = false;
68
					break;
69
				}
70
			}
71
			if (flag) {
72
				ans--;
73
				break;
74
			}
75
		}
76
	}
77
	printf("%.6lf\n", 1. * (n - ans) / n);
78
	return 0;
79
}