题目描述
在孟加拉国有 nnn 个城市,所有城市之间都有双向道路相连,总共有 n×(n−1)2\frac{n \times (n-1)}{2}2n×(n−1) 条道路。其中许多道路被邪恶势力控制。Masud Rana\texttt{Masud Rana}Masud Rana 是孟加拉国反情报部门的一名勇敢间谍,他正在执行一项新任务。
城市 aaa 能安全到达城市 bbb 的条件是:存在一条从 aaa 到 bbb 的路径,且该路径上的所有道路都不受邪恶势力控制。初始时有 mmm 条道路是安全的。Masud Rana\texttt{Masud Rana}Masud Rana 的任务是摧毁一些道路上的邪恶势力,确保所有城市都能通过安全道路相互到达。
Masud Rana\texttt{Masud Rana}Masud Rana 采用了一种特殊策略:
- 他从城市 111 开始任务
- 每天早上,他从当前所在城市随机选择一个其他城市(通过直接连接的道路)
- 在访问过程中,如果该道路有邪恶势力,他会摧毁它们使道路变得安全
- 到达新城市后,他在那里停留到第二天早上
- 然后检查是否所有城市都能安全互达,如果已完成则任务结束,否则重复此过程
我们需要计算 Masud Rana\texttt{Masud Rana}Masud Rana 完成任务的期望天数。
输入格式
第一行包含整数 TTT (T≤100T \leq 100T≤100) 表示测试用例数量。
每个测试用例:
- 第一行:两个整数 NNN (1≤N≤301 \leq N \leq 301≤N≤30) 和 MMM (0≤M≤N×(N−1)20 \leq M \leq \frac{N \times (N-1)}{2}0≤M≤2N×(N−1))
- 接下来 MMM 行:每行两个整数 aaa 和 bbb,表示城市 aaa 和 bbb 之间的道路初始就是安全的
输出格式
对于每个测试用例,输出 Case #: X.XXXXXX,其中 # 是用例编号,X.XXXXXX 是期望天数(误差不超过 10−610^{-6}10−6)。
题目分析
问题本质
这个问题可以建模为一个随机游走和连通分量合并的过程:
- 初始状态:根据给定的安全道路,城市形成若干个连通分量
- 随机过程:每次从当前城市随机选择另一个城市移动
- 状态转移:
- 如果移动到同一连通分量内的城市:状态不变,花费 111 天
- 如果移动到不同连通分量的城市:合并两个连通分量,花费 111 天
- 终止条件:所有城市在同一个连通分量中
关键观察
- 状态简化:在同一个连通分量内,具体在哪个城市不重要,重要的是该分量的大小
- 概率计算:
- 移动到大小为 sss 的连通分量的概率为 sn−1\frac{s}{n-1}n−1s
- 移动到同一连通分量内的概率为 当前分量大小−1n−1\frac{\text{当前分量大小}-1}{n-1}n−1当前分量大小−1
- 期望递推:这是一个典型的马尔可夫过程,可以用动态规划求解
解题思路
我们使用记忆化搜索(DFS\texttt{DFS}DFS 配合 memoization\texttt{memoization}memoization)来解决这个问题:
-
状态表示:(currentSize,otherComps)(currentSize, otherComps)(currentSize,otherComps)
- currentSizecurrentSizecurrentSize:当前所在连通分量的大小
- otherCompsotherCompsotherComps:其他连通分量的大小列表(排序后)
-
状态转移:
- 基础情况:如果没有其他连通分量,返回 000(任务已完成)
- 移动到同一分量:概率 psame=currentSize−1n−1p_{same} = \frac{currentSize-1}{n-1}psame=n−1currentSize−1,状态不变
- 移动到其他分量:对于每个大小为 sss 的其他分量,概率 p=sn−1p = \frac{s}{n-1}p=n−1s,新状态为 (currentSize+s,otherComps∖{s})(currentSize + s, otherComps \setminus \{s\})(currentSize+s,otherComps∖{s})
-
期望方程:
设 EEE 为当前状态的期望天数,则:
E=psame×(1+E)+∑pi×(1+Ei) E = p_{same} \times (1 + E) + \sum p_i \times (1 + E_i) E=psame×(1+E)+∑pi×(1+Ei)
整理得:
E=psame+∑pi×(1+Ei)1−psame E = \frac{p_{same} + \sum p_i \times (1 + E_i)}{1 - p_{same}} E=1−psamepsame+∑pi×(1+Ei)
其中 EiE_iEi 是移动到第 iii 个其他分量后的期望天数。
算法复杂度
- 状态数量:连通分量的划分数,对于 n≤30n \leq 30n≤30 是可接受的
- 每个状态:最多有 n−1n-1n−1 个转移
- 使用 map\texttt{map}map 记忆化:避免重复计算
代码实现
// Masud Rana
// UVa ID: 11600
// Verdict: Accepted
// Submission Date: 2025-11-20
// UVa Run Time: 0.000s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 35;
int n, m;
int parent[MAXN];
int compSize[MAXN];
int find(int x) { return parent[x] == x ? x : parent[x] = find(parent[x]); }
void unite(int x, int y) {
x = find(x), y = find(y);
if (x == y) return;
if (compSize[x] < compSize[y]) swap(x, y);
parent[y] = x;
compSize[x] += compSize[y];
}
map<pair<int, vector<int>>, double> dp;
double solve(int currentSize, vector<int> otherComps) {
if (otherComps.empty()) return 0.0;
sort(otherComps.begin(), otherComps.end());
auto state = make_pair(currentSize, otherComps);
if (dp.count(state)) return dp[state];
int totalChoices = n - 1;
double sameCompProb = (currentSize - 1.0) / totalChoices;
double expectedOther = 0.0;
for (int i = 0; i < otherComps.size(); i++) {
double prob = otherComps[i] * 1.0 / totalChoices;
vector<int> newOtherComps(otherComps);
int newCurrentSize = currentSize + otherComps[i];
newOtherComps.erase(newOtherComps.begin() + i);
expectedOther += prob * (1.0 + solve(newCurrentSize, newOtherComps));
}
double result = (sameCompProb + expectedOther) / (1.0 - sameCompProb);
return dp[state] = result;
}
int main() {
int T;
cin >> T;
for (int caseNum = 1; caseNum <= T; caseNum++) {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
parent[i] = i;
compSize[i] = 1;
}
for (int i = 0; i < m; i++) {
int a, b;
cin >> a >> b;
unite(a, b);
}
map<int, int> compMap;
int startCompRoot = find(1);
for (int i = 1; i <= n; i++) compMap[find(i)]++;
int currentSize = compMap[startCompRoot];
vector<int> otherComps;
for (auto& it : compMap)
if (it.first != startCompRoot)
otherComps.push_back(it.second);
dp.clear();
double expectedDays = solve(currentSize, otherComps);
cout << "Case " << caseNum << ": " << fixed << setprecision(8) << expectedDays << endl;
}
return 0;
}
总结
本题的关键在于将实际问题抽象为连通分量的随机合并过程,并通过动态规划计算期望值。状态表示的选择对算法的效率至关重要,我们通过分离当前分量和其他分量,大大简化了状态表示和转移过程。
这种将图连通性问题转化为概率 DP\texttt{DP}DP 的方法在图论和随机过程结合的问题中非常有用,值得深入理解和掌握。

165

被折叠的 条评论
为什么被折叠?



