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

前言

题目链接:洛谷Hydro & bzoj

题意简述

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

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

题目分析

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

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

设缩点后,入度为 0 的点有 yzh 个,我们所求的不死的概率就是 nyzhn。考虑一个点,落在 nyzh 个点中的概率。

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

时间复杂度线性 Θ(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 @   XuYueming  阅读(61)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示