N 個人中藏有一名殺手,警察可查證任意一人。查證平民會獲得其認識的人的身份信息;查證殺手則遊戲失敗。每人是殺手概率均等。目標:在保證警察安全前提下,最大化確定殺手的概率。
若警察需查證 x 人才能確定殺手,則安全概率 = 1 - x/N。因為殺手均勻分布,查證 x 人不包含殺手的概率即為 (N-x)/N。關鍵在於求最少需查證多少人。
縮點後的 DAG 中,入度為 0 的節點 (源點) 必須被查證,因為無其他節點能提供其信息。答案初步為:1 - (源點數 / N)。
若存在入度為 0 且出度指向的節點入度 > 1 的源點(即其信息可被其他途徑間接獲得),則該節點可作為最後推斷,無需直接查證。此時源點數 -1,概率提升。
to[i]==i && in[i]==0 為源點,計數 s。s--。1.0 - (double)s / N,保留 6 位小數。int dfn[N], low[N], stk[N], top, idx, scc_cnt;
int belong[N], scc_size[N];
bool in_stk[N];
void tarjan(int u) {
dfn[u] = low[u] = ++idx;
stk[++top] = u; in_stk[u] = true;
for (int v : adj[u]) {
if (!dfn[v]) {
tarjan(v);
low[u] = min(low[u], low[v]);
} else if (in_stk[v]) {
low[u] = min(low[u], dfn[v]);
}
}
if (dfn[u] == low[u]) {
++scc_cnt;
int v;
do {
v = stk[top--];
in_stk[v] = false;
belong[v] = scc_cnt;
scc_size[scc_cnt]++;
} while (v != u);
}
}
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100010, MAXM = 300010;
struct Edge { int to, next; } e[MAXM];
int head[MAXN], ecnt;
int n, m;
int dfn[MAXN], low[MAXN], stk[MAXN], top, idx, scc_cnt;
int belong[MAXN], scc_size[MAXN];
bool in_stk[MAXN];
int in_deg[MAXN];
void add_edge(int u, int v) {
e[++ecnt] = {v, head[u]};
head[u] = ecnt;
}
void tarjan(int u) {
dfn[u] = low[u] = ++idx;
stk[++top] = u; in_stk[u] = true;
for (int i=head[u]; i; i=e[i].next) {
int v = e[i].to;
if (!dfn[v]) {
tarjan(v);
low[u] = min(low[u], low[v]);
} else if (in_stk[v]) {
low[u] = min(low[u], dfn[v]);
}
}
if (dfn[u] == low[u]) {
++scc_cnt;
int v;
do {
v = stk[top--];
in_stk[v] = false;
belong[v] = scc_cnt;
scc_size[scc_cnt]++;
} while (v != u);
}
}
int main() {
scanf("%d%d", &n, &m);
for (int i=1; i<=m; ++i) {
int u, v; scanf("%d%d", &u, &v);
add_edge(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=e[i].next) {
int v = e[i].to;
if (belong[u] != belong[v]) in_deg[belong[v]]++;
}
}
int source_cnt = 0;
bool can_reduce = false;
for (int i=1; i<=scc_cnt; ++i) {
if (in_deg[i] == 0) {
source_cnt++;
// 檢查是否為可省略的單點源點
if (!can_reduce && scc_size[i] == 1) {
bool all_out_deg_gt1 = true;
// 找到該SCC對應的原圖節點
int u = 0;
for (int j=1; j<=n; ++j) if (belong[j]==i) { u=j; break; }
for (int k=head[u]; k; k=e[k].next) {
if (in_deg[belong[e[k].to]] == 1) { // 唯一入邊來自自身
all_out_deg_gt1 = false; break;
}
}
if (all_out_deg_gt1) can_reduce = true;
}
}
}
double ans = 1.0 - (double)(source_cnt - can_reduce) / n;
printf("%.6f\n", ans);
return 0;
}
此題巧妙將概率問題轉化為圖論最小覆蓋問題,展示了 Tarjan 算法在實際建模中的強大應用能力。