UVa 11680 Dragster

题目分析

本题是一个基于树形结构的概率计算问题,涉及淘汰赛制锦标赛。我们需要计算特定选手(Rubens\texttt{Rubens}Rubens,编号为111)赢得整个锦标赛的概率。

问题特点

  1. 淘汰赛制:比赛采用单败淘汰制,每场比赛只有一名胜者
  2. 概率矩阵:给出了任意两名选手之间的胜负概率
  3. 树形结构:比赛过程可以表示为一棵二叉树
  4. 复杂编号:选手编号为111NNN,比赛编号为N+1N+1N+12×N−12 \times N - 12×N1

输入格式解析

  • 第一行:整数NNN表示选手数量
  • 接下来NNN行:N×NN \times NN×N的概率矩阵,M[i][j]M[i][j]M[i][j]表示选手iii战胜选手jjj的概率
  • 接下来N−1N-1N1行:比赛描述,每行两个整数表示比赛的双方

关键难点:比赛描述中的编号如果大于NNN,表示引用之前某场比赛的胜者。具体来说,编号kkkk>Nk > Nk>N)表示第k−Nk-NkN行描述的比赛。

解题思路

核心算法:树形动态规划

我们使用深度优先遍历(DFS\texttt{DFS}DFS)结合动态规划来解决这个问题。

状态定义

winProb[u][i]winProb[u][i]winProb[u][i]表示在节点uuu所代表的子树中,选手iii能够到达节点uuu的概率。

  • 如果uuu是叶子节点(选手节点),则:

    • winProb[u][u]=1.0winProb[u][u] = 1.0winProb[u][u]=1.0(该选手肯定能到达自己)
    • winProb[u][j]=0.0winProb[u][j] = 0.0winProb[u][j]=0.0(对于j≠uj \neq uj=u
  • 如果uuu是内部节点(比赛节点),则选手iii能够到达uuu有两种情况:

    1. 选手iii来自左子树并赢得与右子树胜者的比赛
    2. 选手iii来自右子树并赢得与左子树胜者的比赛
状态转移

对于比赛节点uuu,设其左右子节点分别为leftleftleftrightrightright

winProb[u][i]=∑j=1N(winProb[left][i]×winProb[right][j]×prob[i][j])+∑j=1N(winProb[right][i]×winProb[left][j]×prob[i][j]) winProb[u][i] = \sum_{j=1}^{N} \left(winProb[left][i] \times winProb[right][j] \times prob[i][j]\right) + \sum_{j=1}^{N} \left(winProb[right][i] \times winProb[left][j] \times prob[i][j]\right) winProb[u][i]=j=1N(winProb[left][i]×winProb[right][j]×prob[i][j])+j=1N(winProb[right][i]×winProb[left][j]×prob[i][j])

解释

  • 第一项:选手iii从左子树晋级,并战胜右子树中的所有可能对手jjj
  • 第二项:选手iii从右子树晋级,并战胜左子树中的所有可能对手jjj

算法步骤

  1. 读取输入:包括选手数量、概率矩阵和比赛描述
  2. 构建比赛树
    • 选手编号:111NNN
    • 比赛编号:N+1N+1N+12×N−12 \times N - 12×N1
    • 为每个比赛节点建立子节点关系
  3. 寻找根节点:没有父节点的比赛节点即为根节点
  4. 执行 DFS\texttt{DFS}DFS:从根节点开始进行深度优先遍历,计算概率
  5. 输出结果winProb[root][1]winProb[root][1]winProb[root][1]即为Rubens获胜的概率

时间复杂度

  • 状态数量:O(N2)O(N^2)O(N2)
  • 状态转移:O(N2)O(N^2)O(N2)
  • 总时间复杂度:O(N4)O(N^4)O(N4),在N≤300N \leq 300N300的情况下可以通过

代码实现

// Dragster
// UVa ID: 11680
// Verdict: Accepted
// Submission Date: 2025-11-24
// UVa Run Time: 0.130s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 605;
vector<int> children[MAXN];
double prob[MAXN][MAXN];
double winProb[MAXN][MAXN];
int n;

void dfs(int u) {
    if (u <= n) {
        for (int i = 1; i <= n; i++) winProb[u][i] = 0.0;
        winProb[u][u] = 1.0;
        return;
    }
    
    int left = children[u][0];
    int right = children[u][1];
    dfs(left);
    dfs(right);
    
    for (int i = 1; i <= n; i++) winProb[u][i] = 0.0;
    
    for (int i = 1; i <= n; i++) {
        if (winProb[left][i] > 0) {
            double sum = 0.0;
            for (int j = 1; j <= n; j++)
                sum += winProb[right][j] * prob[i][j];
            winProb[u][i] += winProb[left][i] * sum;
        }
    }
    
    for (int i = 1; i <= n; i++) {
        if (winProb[right][i] > 0) {
            double sum = 0.0;
            for (int j = 1; j <= n; j++)
                sum += winProb[left][j] * prob[i][j];
            winProb[u][i] += winProb[right][i] * sum;
        }
    }
}

int main() {
    while (cin >> n && n) {
        for (int i = 1; i <= 2 * n; i++) children[i].clear();
        
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                cin >> prob[i][j];
        
        vector<pair<int, int>> matches(n - 1);
        for (int i = 0; i < n - 1; i++)
            cin >> matches[i].first >> matches[i].second;
        
        vector<bool> hasParent(2 * n + 1, false);
        
        for (int i = 0; i < n - 1; i++) {
            int raceId = n + 1 + i;
            int left = matches[i].first;
            int right = matches[i].second;
            
            children[raceId].push_back(left);
            children[raceId].push_back(right);
            hasParent[left] = true;
            hasParent[right] = true;
        }
        
        int root = 0;
        for (int i = n + 1; i <= 2 * n - 1; i++) {
            if (!hasParent[i]) {
                root = i;
                break;
            }
        }
        
        dfs(root);
        cout << fixed << setprecision(6) << winProb[root][1] << endl;
    }
    return 0;
}

总结

本题的关键在于理解比赛树的结构和概率的传递方式。通过树形动态规划,我们可以高效地计算出每个选手在不同比赛阶段的晋级概率。算法虽然时间复杂度较高,但在题目约束下完全可行。

核心技巧

  1. 正确解析比赛编号的引用关系
  2. 使用 DFS\texttt{DFS}DFS 进行树形遍历
  3. 合理设计动态规划状态和转移方程

这种树形概率 DP\texttt{DP}DP 的方法在竞赛中经常出现,掌握后可以解决许多类似的问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值