首页 > 代码库 > ZOJ3329 概率DP变形

ZOJ3329 概率DP变形

哇哦,感觉有点难哦,三个骰子,分别具有k1,k2,k3个面,抛骰子,若向上的一面分别对应为a,b,c,那么得分归0,否则得分加上三个骰子向上那一面数字之和,求得分超过n的时候抛骰子的次数的期望

一开始很容易想到常规的做法,以目标状态为边界,当前状态到目标状态所需要的期望为方程,dp[i]代表 当前到目标分数的期望,这是发现状态转移是这个样子的

dp[i] = dp[0] * p0 + sigma(pk * dp[i + k]) + 1;

p0,代表抛到分数归0 的概率,pk代表抛到分数为k的概率,这时候发现无法去做,因为我们所要求的答案就是dp[0],而每一步的状态转移里都包括了dp[0],同时前面是累积求和,这就相当于有个“环”一样,就像方程两边都有要求的未知数,但是无法移到同一边去,卡死了,首先觉得自己可能方程假设的不好,但是想了很久也没有其它的方程比这个合理了,后来看了这个博客:http://blog.csdn.net/xingyeyongheng/article/details/25639827
步骤讲解还是比较详细的,我是没有想到这个方面去,看了他的假设以后我就自己用草稿纸去往下推了,写了一大堆还是写出来了

首先假设dp[i] = A[i] * dp[0] + B[i];

这里发现dp[0] = A[0] * dp[0] + B[0];

目标就是要求出A[0],B[0],所以这个方程 要跟状态转移联系在一起的,

那么dp[i + k] = A[i + k] * dp[0] + B[i + k],然后把这个代入状态转移方程中去

dp[i] = dp[0] * p0 + sigma(pk*(A[i + k] * dp[0] + B[i + k])) + 1;化简一下

dp[i] = (sigma(pk * A[i + k]) + p0) * dp[0] + sigma(pk*B[i + k]) + 1;

那么A[i] = (sigma(pk * A[i + k]) + p0) ;

B[i] = sigma(pk*B[i + k]) + 1;

这里可以递推出A[0],B[0]

然后再回到假设方程去考虑

dp[0] = A[0] * dp[0] + B[0]

这里发现dp[0] 其实在这里不算是变量了,跟一个常数一样,所以上面才那样做出假设的,没有想到这点所以不知道如何处理,唉~


int t;

int k1,k2,k3,aa,bb,cc,n;

double pp[50 + 5];

double AA[500 + 55],BB[500 + 55];

void init() {
	memset(pp,0.00,sizeof(pp));
	memset(AA,0.00,sizeof(AA));
	memset(BB,0.00,sizeof(BB));
}

bool input() {
	while(cin>>n>>k1>>k2>>k3>>aa>>bb>>cc) {
		pp[0] = 1.0/k1 * 1.0/k2 * 1.0/k3;
		return false;
	}
	return true;
}

void cal() {
	for(int i=1;i<=k1;i++)
		for(int j=1;j<=k2;j++)
			for(int k=1;k<=k3;k++)
				if(i != aa || j != bb || k != cc)
					pp[i + j + k] += 1.0/k1 * 1.0/k2 * 1.0/k3;
	for(int i=n;i>=0;i--) {
		AA[i] = 0.00;
		BB[i] = 0.00;
		for(int k=1;k<= k1 + k2 + k3;k++) {
			AA[i] += pp[k] * AA[i + k];
			BB[i] += pp[k] * BB[i + k];
		}
		AA[i] += pp[0];
		BB[i] += 1.00; 
	}
	double ans = BB[0]/(1 - AA[0]);
	printf("%.10lf\n",ans);
}

void output() {

}

int main() {
	cin>>t;
	while(t--) {
		init();
		if(input())return 0;
		cal();
		output();
	}
	return 0;
}


ZOJ3329 概率DP变形