题目分析
本题是一个基于树形结构的概率计算问题,涉及淘汰赛制锦标赛。我们需要计算特定选手(Rubens\texttt{Rubens}Rubens,编号为111)赢得整个锦标赛的概率。
问题特点
- 淘汰赛制:比赛采用单败淘汰制,每场比赛只有一名胜者
- 概率矩阵:给出了任意两名选手之间的胜负概率
- 树形结构:比赛过程可以表示为一棵二叉树
- 复杂编号:选手编号为111到NNN,比赛编号为N+1N+1N+1到2×N−12 \times N - 12×N−1
输入格式解析
- 第一行:整数NNN表示选手数量
- 接下来NNN行:N×NN \times NN×N的概率矩阵,M[i][j]M[i][j]M[i][j]表示选手iii战胜选手jjj的概率
- 接下来N−1N-1N−1行:比赛描述,每行两个整数表示比赛的双方
关键难点:比赛描述中的编号如果大于NNN,表示引用之前某场比赛的胜者。具体来说,编号kkk(k>Nk > Nk>N)表示第k−Nk-Nk−N行描述的比赛。
解题思路
核心算法:树形动态规划
我们使用深度优先遍历(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有两种情况:
- 选手iii来自左子树并赢得与右子树胜者的比赛
- 选手iii来自右子树并赢得与左子树胜者的比赛
状态转移
对于比赛节点uuu,设其左右子节点分别为leftleftleft和rightrightright:
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=1∑N(winProb[left][i]×winProb[right][j]×prob[i][j])+j=1∑N(winProb[right][i]×winProb[left][j]×prob[i][j])
解释:
- 第一项:选手iii从左子树晋级,并战胜右子树中的所有可能对手jjj
- 第二项:选手iii从右子树晋级,并战胜左子树中的所有可能对手jjj
算法步骤
- 读取输入:包括选手数量、概率矩阵和比赛描述
- 构建比赛树:
- 选手编号:111到NNN
- 比赛编号:N+1N+1N+1到2×N−12 \times N - 12×N−1
- 为每个比赛节点建立子节点关系
- 寻找根节点:没有父节点的比赛节点即为根节点
- 执行 DFS\texttt{DFS}DFS:从根节点开始进行深度优先遍历,计算概率
- 输出结果: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 300N≤300的情况下可以通过
代码实现
// 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;
}
总结
本题的关键在于理解比赛树的结构和概率的传递方式。通过树形动态规划,我们可以高效地计算出每个选手在不同比赛阶段的晋级概率。算法虽然时间复杂度较高,但在题目约束下完全可行。
核心技巧:
- 正确解析比赛编号的引用关系
- 使用 DFS\texttt{DFS}DFS 进行树形遍历
- 合理设计动态规划状态和转移方程
这种树形概率 DP\texttt{DP}DP 的方法在竞赛中经常出现,掌握后可以解决许多类似的问题。
277

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



