天上掉馅饼 期望dp+状压dp

天上掉馅饼
(bonus)
题目描述
小G进入了一个神奇的世界,在这个世界,天上会掉下一些馅饼。今天,天上会随机掉下k个馅饼。
每次天上掉下馅饼,小G可以选择吃或者不吃(必须在下一个馅饼掉下来之前作出选择,并且现在决定不吃的话以后也不能吃)。
馅饼有n种不同的馅,根据物理定律,天上掉下这n种馅饼的概率相同且相互独立。然而,每一种馅饼i都有一个前提馅饼集合Si。只有当 Si 中的馅饼都吃过之后,才能吃第i种馅饼。比如说,韭菜馅馅饼的S中有白菜猪肉馅饼和鲜虾馅饼,那么小G只有在吃过白菜猪肉馅饼和鲜虾馅饼之后,才能吃韭菜馅的馅饼。
同时,每个馅饼还有一个美味值Pi。今天一天小G的幸福度,等于小G吃到的所有馅饼的美味值之和。注意:Pi 可能是负数。
现在考虑,采用最优策略的前提下,小G这一天期望的幸福度是多少?
输入格式(bonus.in)
第一行两个正整数k和n,表示馅饼的数量和种类。
以下n行,每行若干个数,描述一种馅饼。其中第一个数代表美味值,随后的整数表示该馅饼的前提馅饼,以0结尾。
输出格式(bonus.out)
输出一个实数,保留6位小数,即在最优策略下期望的幸福度。
输入样例
1 2
1 0
2 0
1 2 0 2
输出样例
1.500000
数据范围
对于20% 的数据,所有的馅饼都没有“前提馅饼”。
对于50% 的数据,1 ≤ k ≤ 10,1 ≤ n ≤ 10。

对于100%的数据,1 ≤ k ≤ 100,1 ≤ n ≤ 15,美味度为[-106; 106]的整数。

题解:对于每一种馅饼,用二进制位上的1来表示是否出现,由于前面是否吃某种馅饼对后面吃某些馅饼是有影响的,所以采用倒着推的方式。倒着推的dp基本可以用记忆化搜索,当前状态扩展出其他状态,由于最终扩展出来的状态非常多不好统计答案,所以才有回溯时从末状态返回初状态。

总结:倒着推的dp通常都可以记忆化搜索,其实dp就是记忆化搜索,理论上dfs可以替代dp,但一定要搞清楚记忆的是什么,值得注意的是没记录状态是否已经计算过一定要用bool类型进行判断,不能通过该状态是否有值来判断(超时)(很玄学)。

记忆化搜索:

//记忆化dfs代替状态压缩dp倒着推 
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
int n,t;
using namespace std;
int date[20][20],num[20],val[20];
double dp[200][500000];
int pre[2000];
bool vis[200][300000];
double dfs(int dep,int mat_)
{
	if(vis[dep][mat_]) return dp[dep][mat_];//非常重要的剪枝!!!
	if(dep>t) return 0.00;
	vis[dep][mat_]=1;
	for(int i=1;i<=n;i++) 
		if( ( mat_ & pre[i] ) == pre[i] ) 
		{
			if(val[i]>=0) dp[dep][mat_]+=(double)(dfs(dep+1,mat_|(1<<(i-1)))+(double)val[i]);
			else dp[dep][mat_] += max( dfs(dep+1,mat_) , (double)( dfs(dep+1,(mat_|(1<<(i-1))) ) + (double)val[i]) );
		}else dp[dep][mat_]+=dfs(dep+1,mat_);
	dp[dep][mat_]/=(double)n;
	return dp[dep][mat_];
}


int main()
{
//	freopen("bonus.in","r",stdin);
//	freopen("bonus.out","w",stdout);
	memset(pre,0,sizeof(pre));
	int x;
	scanf("%d %d",&t,&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&val[i]);
		while(scanf("%d",&x) && x) pre[i] |= (1<<(x-1));
	}
	printf("%.6lf",dfs(1,0));
	return 0;
}

/*	
44 15
-221964 12 9 0
558098 3 9 0
-765634 4 8 12 10 5 0
-113034 9 0
711128 6 12 0
-610384 12 0
-280873 13 6 10 0
705891 11 5 10 9 0
112653 6 10 0
-635146 5 8 2 15 3 0
-85056 4 0
-196879 0
105600 0
768292 5 11 7 0
-328587 4 0
*/
dp:

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#define N 500000
using namespace std;
double dp[105][N];
int pre[20],val[20];
int t,n;
int main()
{
//	freopen("bonus.in","r",stdin);
//	freopen("bonus.out","w",stdout);
	int x;
	scanf("%d%d",&t,&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&val[i]);
		while(scanf("%d",&x) && x) pre[i]|=(1<<(x-1));
	}
	for(int i=1;i<=t;i++)
	    for(int j=0;j < (1<<n);j++)
		{
	        for(int k=1;k<=n;k++)
	            if( (pre[k]&j) == pre[k] ) dp[i][j]+=max(dp[i-1][j],dp[i-1][j|(1<<(k-1))]+(double)val[k]);
				else dp[i][j]+=dp[i-1][j];
			dp[i][j]/=(double)n;
		}
	printf("%.6lf",dp[t][0]);
	return 0;
} 



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值