P2973 [USACO10HOL] Driving Out the Piggies G 解题报告


P2973 [USACO10HOL] Driving Out the Piggies G 解题报告

1. 题目大意

想象一下,我们有一张由 \(N\) 个城市和 \(M\) 条双向道路组成的地图。我们在城市 1 放置了一颗“随机臭弹”。

这颗臭弹的行为模式如下:

  • 每一小时,当臭弹位于某个城市时,它有 \(\frac{P}{Q}\) 的概率原地爆炸,污染当前所在的城市。
  • 如果它没有爆炸(概率为 \(1 - \frac{P}{Q}\)),它会从当前城市的所有道路中,随机选择一条,移动到下一个城市。每条道路被选中的概率是均等的。

我们的任务是,计算出对于每一个城市 \(i\)(从 1 到 \(N\)),这颗臭弹最终在该城市爆炸的总概率是多少。

2. 思路分析:从无限到有限

核心难点: 臭弹的移动和爆炸过程可以无限进行下去。比如,它可能在城市1和城市2之间来来回回走很多次才爆炸。我们不可能通过模拟每一种路径来计算概率,因为路径有无限多条。

解决思路: 当我们遇到一个可能无限进行的过程时,一个常见的解决策略是建立方程。我们不关心过程具体走了多少步,只关心最终的“稳态”结果。

让我们借鉴一下题解中那个有趣的小例子:

三个人玩“手心手背”,如果两个人出的手势一样,他们就胜出。如果三个人手势都不一样(比如一人心、一人背、一人侧),就重来。问小明和小红一起胜出的概率是多少?

设小明和小红胜出的概率为 \(x\)
在任意一轮中:

  • 他们直接胜出的概率是 \(\frac{1}{4}\)
  • 游戏重开的概率也是 \(\frac{1}{4}\)。如果游戏重开,那么未来小明和小红胜出的概率依然是 \(x\)

所以,我们可以列出方程:

\[x = (\text{这一轮直接胜出的概率}) + (\text{这一轮重开的概率}) \times (\text{未来胜出的概率}) \]

\[x = \frac{1}{4} + \frac{1}{4}x \]

解这个简单的方程,就能得到 \(x = \frac{1}{3}\)

应用到本题: 我们可以用同样的方式来思考臭弹问题。我们设 \(Prob_i\) 为臭弹最终在城市 \(i\) 爆炸的总概率。这个 \(Prob_i\) 就是我们要求的答案。

现在,我们来分析 \(Prob_i\) 是如何构成的。臭弹要在城市 \(i\) 爆炸,它必须先到达城市 \(i\)

让我们从“概率流”的角度来思考。

  • 初始状态:在游戏开始时,有 \(100\%\) 的概率(也就是概率值为 1)在城市 1。
  • 概率的流动:在每个城市,一部分概率会因为爆炸而“固化”下来,成为最终结果。另一部分概率会因为移动而“流向”相邻的城市。

我们来为每个城市建立一个关于概率的平衡方程。

对于城市 \(i\)\(i \neq 1\)):
臭弹要在这里爆炸,它必须是从某个相邻的城市 \(j\) 移动过来的。

  • 考虑一个与城市 \(i\) 相邻的城市 \(j\)。臭弹在城市 \(j\) 的整个过程中,总共贡献了 \(Prob_j\) 的爆炸概率。
  • 这意味着,在到达城市 \(j\) 后,有 \(1 - \frac{P}{Q}\) 的概率没有爆炸,而是选择移动。
  • 城市 \(j\)\(d_j\) 条路(\(d_j\)是城市 \(j\) 的度),所以选择去城市 \(i\) 的概率是 \(\frac{1}{d_j}\)
  • 因此,从城市 \(j\) "流向" 城市 \(i\) 并最终在 \(i\) 爆炸的这部分概率,可以表示为:\(Prob_j \times (1-\frac{P}{Q}) \times \frac{1}{d_j}\)
  • 把所有与 \(i\) 相邻的城市 \(j\) 的贡献加起来,就是 \(Prob_i\) 的全部来源。

所以,对于任意一个非起始点的城市 \(i\)\(i \neq 1\)),我们有:

\[Prob_i = \sum_{(i,j) \in E} \left( Prob_j \times (1 - \frac{P}{Q}) \times \frac{1}{d_j} \right) \]

其中 \(E\) 表示所有道路的集合,\((i,j) \in E\) 表示城市 \(i\)\(j\) 之间有路。

对于城市 1:
城市 1 比较特殊,因为它是一切的起点。

  • 来源一(初始爆炸):臭弹一开始就在城市 1,它可能第一小时就原地爆炸。这个概率是 \(\frac{P}{Q}\)
  • 来源二(从别处回来):和上面的分析一样,臭弹也可能从相邻的城市 \(j\) 移动回来,再在城市 1 爆炸。

所以,对于起始点城市 1,我们有:

\[Prob_1 = \frac{P}{Q} + \sum_{(1,j) \in E} \left( Prob_j \times (1 - \frac{P}{Q}) \times \frac{1}{d_j} \right) \]

3. 建立方程组与求解

现在我们有了 \(N\) 个未知数 (\(Prob_1, Prob_2, ..., Prob_N\)) 和 \(N\) 个方程。这是一个标准的N元一次方程组。我们的任务就是解这个方程组。

为了方便计算机求解,我们把所有未知项移到等式左边,常数项移到右边:

  1. 对于城市 1 的方程:

    \[Prob_1 - \sum_{(1,j) \in E} \left( (1 - \frac{P}{Q}) \frac{1}{d_j} \right) Prob_j = \frac{P}{Q} \]

  2. 对于城市 \(i \neq 1\) 的方程:

    \[Prob_i - \sum_{(i,j) \in E} \left( (1 - \frac{P}{Q}) \frac{1}{d_j} \right) Prob_j = 0 \]

这是一个形如 \(A\vec{x} = \vec{b}\) 的线性方程组,其中 \(\vec{x}\) 是我们要求的概率向量 \((Prob_1, ..., Prob_N)^T\)。解决这种问题的经典算法就是高斯消元法(Gaussian Elimination)

4. 代码解析

我们来看一下题解提供的 C++ 代码是如何实现这个过程的。

#include<bits/stdc++.h> 
#define maxn 310
#define eps (1e-6)
using namespace std;
int n,m,p,q,x,y;
int f[310][310]; // 邻接矩阵,f[i][j]=1 表示i和j有路
int in[310];      // 存储每个点的度
double a[maxn][maxn]; // 增广矩阵,用于高斯消元

// 高斯-若尔当消元法
void gauss_jordan(){
	// ... (标准的高斯消元模板)
}

int main(){
	// 1. 输入数据并建图
	scanf("%d%d%d%d",&n,&m,&p,&q);
	for(int i=1;i<=m;i++){
		scanf("%d%d",&x,&y);
		f[x][y]=f[y][x]=true; // 标记x和y之间有路
		in[x]++;in[y]++;      // 对应度数加1
	}

	// 2. 构建增广矩阵 a
	// 我们的方程是 Prob_i - sum(c_j * Prob_j) = b_i
	// 矩阵 a[i][j] 存 Prob_j 在第 i 个方程里的系数
	// a[i][n+1] 存第 i 个方程的常数项 b_i

	// 设置方程1的常数项
	a[1][n+1] = 1.0 * p / q; 
	// 其他方程的常数项默认为0,不需要显式设置

	for(int i=1;i<=n;i++){
		// Prob_i 的系数是 1
		a[i][i] = 1;
		
		// 遍历所有点j,看它是不是i的邻居
		for(int j=1;j<=n;j++)
			if(f[i][j]) { // 如果 j 是 i 的邻居
                // Prob_j 的系数是 -(1 - P/Q) / d_j
				a[i][j] = -(1 - (double)p/q) / in[j];
			}
	}
	
	// 3. 调用高斯消元求解
	gauss_jordan();

	// 4. 输出结果
	// 消元后,a[i][n+1] 中存储的就是 Prob_i 的解
	for(int i=1;i<=n;i++)
		printf("%.9lf\n",a[i][n+1]);
		
	return 0;
}

代码核心逻辑剖析:

  1. 建图:使用邻接矩阵 f 和度数数组 in 来存储图的信息。
  2. 构建方程组(增广矩阵a
    • a[i][i] = 1; 对应方程左边的 Prob_i 项,其系数为1。
    • a[i][j] = -(1 - (double)p/q) / in[j]; 对应方程左边的 - \sum (...) 部分。对于第 i 个方程,如果 ji 的邻居,那么 Prob_j 会出现在求和项里,其系数就是 (1-P/Q) * (1/d_j)。移到等式左边后,系数变为负。
    • a[1][n+1] = 1.0 * p / q; 单独设置了第一个方程右边的常数项。其他方程的常数项为0,这是数组初始化的默认值。
  3. 求解gauss_jordan() 函数是一个实现高斯消元的模板,它直接对矩阵 a 进行操作,把解算出来。
  4. 输出:经过高斯消元后,矩阵 a 会变成一个对角矩阵(或者上三角矩阵再回代),解就存在了最后一列 a[i][n+1] 中。直接输出即可。

5. 总结

本题是一个典型的概率与图论结合的问题,并利用数学期望/概率方程来求解。

解题步骤可以归纳为:

  1. 识别问题模型:发现这是一个涉及无限步骤的随机过程,常规的搜索或模拟无法解决。
  2. 建立数学模型:为每个城市的最终爆炸概率 Prob_i 设立未知数。
  3. 列出方程:根据概率的流动和平衡关系,为每个 Prob_i 列出一个线性方程,形成一个 N 元一次方程组。
  4. 求解方程:利用高斯消元法这一标准工具来求解该线性方程组,得到每个未知数的具体值。
  5. 编码实现:将上述过程转化为代码,主要是正确地构建增广矩阵和应用高斯消元模板。

这种“设未知数,列方程”的思想是解决许多复杂概率、期望问题的利器。

posted @ 2025-07-22 15:18  surprise_ying  阅读(8)  评论(0)    收藏  举报