【题解】 UVA10529 Dumb Bones 骨牌


【题解】 UVA10529 Dumb Bones 骨牌

题意

你试图把一些多米诺骨牌排成直线,然后推倒它们。但是如果你在放骨牌的时候不小心把刚放的骨牌碰倒了,它就会把相临的一串骨牌全都碰倒,而你的工作也被部分的破坏了。

比如你已经把骨牌摆成了 DD__DxDDD_D 的形状(侧视图)(其中 D 代表骨牌,下划线和 x 均代表还没有放骨牌的空位),而想要在 x 这个位置再放一块骨牌。它可能会把左边的一块骨牌或右边的三块骨牌碰倒,而你将不得不重新摆放这些骨牌。

给出你要摆放的骨牌数目,以及放骨牌时它向左和向右倒的概率(每一块骨牌都一样)。

为了使期望摆放骨牌的次数最少,你可以采用某种特定的摆放顺序。求这个最少的期望摆放骨牌次数。

算法 1

概率 + 最优解,可以考虑用区间 DP

考虑一个区间。枚举这个区间里最后摆放的一块骨牌的位置,把原区间分割成左右两个子区间。

设原区间期望最少摆放次数为 \(E\) ,左区间为 \(E_1\) ,右区间 \(E_2\) 。此时最后一块骨牌还没有放上去,那么设这个空位的位置是 \(x\)

已知每块骨牌向左倒的概率是 \(P_l\) ,向右是 \(P_r\)

单独看 \(x\) 这一个骨牌,它不倒的概率是 \(1-P_l-P_r\) ,那么使它不倒的期望摆放次数是 \(\frac1{1-P_l-P_r}\)

注意这个次数里:最后一次是不倒的,之前的有些向左有些向右。因为向左的概率是 \(P_l\) ,所以向左倒的次数就是 \(\frac1{1-P_l-P_r}\times P_l\) 。由于每向左倒一次,左区间的就要全部重新摆放。左区间放好一次需要 \(E_1\) 个摆放,所以一共左区间要摆 \(\frac{P_l}{1-P_l-P_r}\times E_1\) 次。同理右区间一共要放置 \(\frac{P_r}{1-P_l-P_r}\times E_2\) 次。

还有就是放 \(x\) 之前需要先把左右区间都摆好一次,共 \(E_1+E_2\) 个摆放。

最后状态转移方程就是

\[E=E_1+E_2+\frac1{1-P_l-P_r}+\frac{P_l\times E_1}{1-P_l-P_r}+\frac{P_r\times E_2}{1-P_l-P_r} \]

于是我们轻松的写出了下面这一坨

int n;
double pl, pr;//向左、向右倒的概率
double f[N][N];//dp 数组(记忆化搜索)

double dfs(int l, int r) {//区间左右端点
	double &E = f[l][r];//使用引用方便又快捷
	if (E > eps) return E;//记忆化
	if (l > r) return E = 0.0;//边界
	if (l == r) return E = 1.0/(1.0-pl-pr);//只放一个
	E = inf;//先来一个极大值
	for (int i=l; i<=r; ++i) {//枚举最后一个摆放的位置 i
		double E1 = dfs(l, i-1);//左区间摆放次数
		double E2 = dfs(i+1, r);//右区间
		E = std::min(
			E,
			E1 + E2 + 1.0/(1.0-pl-pr) + pl*E1/(1.0-pl-pr) + pr*E2/(1.0-pl-pr)//刚才的方程
		);//通过 min 获得最优解
	}
	return E;
}

然后光荣地 T 飞爆零了

算法 2

上面的算法是 \(O(n^3)\) 的,当然过不了,我们考虑降维

因为每块牌都一模一样,显然对于所有长度相同的区间,它们的 \(E\) 都是相同的

把状态重新设置为区间长度,就只有不同长度的区间才需要转移,状态转移方程不变

获得正解

#include <cstdio>
#include <iostream>
using namespace std;

#define N 1030
const double eps = 1e-8;
const double inf = 1e20;

double pl, pr;//向左、向右倒的概率
double f[N];

double dfs(int n) {//长度为 n 的区间
	double &E = f[n], E1, E2;//还是引用
	if (E > -0.5) return E;//让 f[0]=0.0 和 f[1]=1.0/(1.0-pl-pr) 都可以自然转移
	E = inf;
	for (int i=1; i<=n; ++i) {
		E1 = dfs(i-1), E2 = dfs(n-i);//左、右区间的答案
		E = min(
			E,
			E1 + E2 + f[1] + pl*E1*f[1] + pr*E2*f[1]
		);
	}
	return E;
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr);//关闭流同步
	while (true)
	{
		int n;
		cin >> n;
		if (!n) break;
		cin >> pl >> pr;
		for (int i=2; i<=n; ++i) f[i] = -1.0;//注意细节
		f[1] = 1.0 / (1.0 - pl - pr);//同时 f[0]=0.0
		printf("%.2f\n", dfs(n));
	}
	return 0;
}


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM