UVa 11600 Masud Rana

题目描述

在孟加拉国有 nnn 个城市,所有城市之间都有双向道路相连,总共有 n×(n−1)2\frac{n \times (n-1)}{2}2n×(n1) 条道路。其中许多道路被邪恶势力控制。Masud Rana\texttt{Masud Rana}Masud Rana 是孟加拉国反情报部门的一名勇敢间谍,他正在执行一项新任务。

城市 aaa 能安全到达城市 bbb 的条件是:存在一条从 aaabbb 的路径,且该路径上的所有道路都不受邪恶势力控制。初始时有 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 100T100) 表示测试用例数量。

每个测试用例:

  • 第一行:两个整数 NNN (1≤N≤301 \leq N \leq 301N30) 和 MMM (0≤M≤N×(N−1)20 \leq M \leq \frac{N \times (N-1)}{2}0M2N×(N1))
  • 接下来 MMM 行:每行两个整数 aaabbb,表示城市 aaabbb 之间的道路初始就是安全的

输出格式

对于每个测试用例,输出 Case #: X.XXXXXX,其中 # 是用例编号,X.XXXXXX 是期望天数(误差不超过 10−610^{-6}106)。

题目分析

问题本质

这个问题可以建模为一个随机游走连通分量合并的过程:

  1. 初始状态:根据给定的安全道路,城市形成若干个连通分量
  2. 随机过程:每次从当前城市随机选择另一个城市移动
  3. 状态转移
    • 如果移动到同一连通分量内的城市:状态不变,花费 111
    • 如果移动到不同连通分量的城市:合并两个连通分量,花费 111
  4. 终止条件:所有城市在同一个连通分量中

关键观察

  1. 状态简化:在同一个连通分量内,具体在哪个城市不重要,重要的是该分量的大小
  2. 概率计算
    • 移动到大小为 sss 的连通分量的概率为 sn−1\frac{s}{n-1}n1s
    • 移动到同一连通分量内的概率为 当前分量大小−1n−1\frac{\text{当前分量大小}-1}{n-1}n1当前分量大小1
  3. 期望递推:这是一个典型的马尔可夫过程,可以用动态规划求解

解题思路

我们使用记忆化搜索(DFS\texttt{DFS}DFS 配合 memoization\texttt{memoization}memoization)来解决这个问题:

  1. 状态表示(currentSize,otherComps)(currentSize, otherComps)(currentSize,otherComps)

    • currentSizecurrentSizecurrentSize:当前所在连通分量的大小
    • otherCompsotherCompsotherComps:其他连通分量的大小列表(排序后)
  2. 状态转移

    • 基础情况:如果没有其他连通分量,返回 000(任务已完成)
    • 移动到同一分量:概率 psame=currentSize−1n−1p_{same} = \frac{currentSize-1}{n-1}psame=n1currentSize1,状态不变
    • 移动到其他分量:对于每个大小为 sss 的其他分量,概率 p=sn−1p = \frac{s}{n-1}p=n1s,新状态为 (currentSize+s,otherComps∖{s})(currentSize + s, otherComps \setminus \{s\})(currentSize+s,otherComps{s})
  3. 期望方程
    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=1psamepsame+pi×(1+Ei)
    其中 EiE_iEi 是移动到第 iii 个其他分量后的期望天数。

算法复杂度

  • 状态数量:连通分量的划分数,对于 n≤30n \leq 30n30 是可接受的
  • 每个状态:最多有 n−1n-1n1 个转移
  • 使用 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 的方法在图论和随机过程结合的问题中非常有用,值得深入理解和掌握。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值