uva1637:纸牌游戏(记忆化搜索,状压dp , 概率dp , 全概率公式)

题目大意:有36张牌,分成9堆,每堆4张牌。每次可以拿走某两堆顶部的牌,但需要点数相同。如果有多种拿法则等概率的随机的拿。例如,9堆顶部的牌分别为KS,KH,KD,9H,8S,8D,7C,7D,6H,则有5种拿法(KS,KH),(KS,KD).(KH,KD).(8S,8D),(7C,7D),每种拿法的概率均为1/5。如果最后拿完所有牌则游戏成功。按顺序给出每堆牌的四张牌,求成功概率。

解法:设A为拿完牌,Bi为一种拿法,则P(A) = P(A * B1) + P(A * B2) + …+ P(A * Bn) (全概率公式),P(A * Bi)的概率为用Bi这种方案拿牌一路拿牌拿到完的概率。

先考虑9维dfs暴搜所有拿牌方案,每一步都是一个条件概率(在上一步拿完牌的条件下,这一步可拿牌的方案会改变,这里要求的是最后一步刚好拿完牌的概率),因为是古典概型(每一步等概率拿牌),每一步拿牌的概率可以直接算出来(算出当前这一步有多少对牌可以拿,除以这个数),则P(A * Bi)为每一步拿牌的概率相乘,枚举出所有拿牌的方案,概率之和即为答案。

进一步想,其实在搜索过程中会出现许多重复的子问题。例如有两对牌可以拿的时候,如果分先后两次拿完这两对牌(拿完之前没拿其他牌),那么拿牌的顺序有2种,但拿完这两对牌后都会面临同一种剩余牌的状态,而直接搜索要解两次这种状态(最差的情况下重复搜索的次数难以计算)。考虑记忆化搜索来降低复杂度,有9维,可以列9维dp数组,更好的做法时状态压缩,因为每一堆牌只有4张,剩余牌数列状态只有5种,可以转化为5进制。搜索过程和原始的暴搜略有不同:设dp[i]表示当前状态拿完所有牌的概率,记忆化搜索即可(dfs变成了搜当前状态的成功率)。

记忆化搜索的结果是,当前这些的牌拿完的概率,每一步都是一个独立的子问题,可以独立计算,只要考虑将小规模的状态的答案合并转移给更大规模的状态,也就是状态转移。状态转移方程不列出(自己想一想)。

暴搜和记忆化搜索的函数定义的差别(不重要,可以忽略):暴搜搜的是所有拿牌方案,要计算出每一种方案的概率,每一步调用都是条件概率。记忆化搜索是解决所有可能出现的子问题,自底向上合并答案,每一步搜索都是一个全概率公式(都是相同的问题,规模更小)。

复杂度上:5维状态总数有5^9 = 1953125种,每种状态的决策数量最多有12种,理论上界在2e7 左右,实际上很多状态根本不会用到,所以跑得很快

注意这题是多组数据读入

#include<bits/stdc++.h>
using namespace std;
const int maxn = 3e6 + 10;
char s[20][20][20];
bool read() {
	for(int i = 1; i <= 9; i++) {
		for(int j = 1; j <= 4; j++) {
			scanf("%s",s[i][j]);
			if(s[i][j][0] == 0) return false;
		}
	}
	return true;
}
int cur[10];
double dp[maxn];
int vis[maxn];
bool check() {
	for(int i = 1; i <= 9; i++) {
		if(cur[i]) 
			return false;
	}
	return true;
}
int change() {
	int ans = 0;	
	for(int i = 1; i <= 9; i++) {
		ans = ans * 5 + cur[i];
	}
	return ans;
}
double dfs() {
	int tab = change();
	if(vis[tab]) 
		return dp[tab];
	vis[tab] = true;
	if(check()) {
		return dp[tab] = 1;
	}
	double cnt = 0;
	double res = 0;
	for(int i = 1; i <= 9; i++) {
		for(int j = i + 1; j <= 9; j++) {
			if(cur[i] && cur[j] && s[i][cur[i]][0] == s[j][cur[j]][0]) {
				cnt++;	
				cur[i]--;cur[j]--;
				res += dfs();
				cur[i]++;cur[j]++;
			}
		}
	}
	if(cnt) res /= cnt;
 	dp[tab] = res;
	return res;
}
int main() {
	while(1) {
		memset(s,0,sizeof(s));		
		if(!read()) break;
		for(int i = 1; i <= 9; i++)
			cur[i] = 4; 
		memset(dp,0,sizeof(dp));
		memset(vis,0,sizeof(vis));
		double ans = dfs();
		printf("%.6lf\n",ans); 
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值