【題解】 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