天上掉馅饼 期望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;
} 



给定的参考引用中未提及“dp++”的相关内容。在IT领域,“dp”常见的有动态规划(Dynamic Programming)、缩动态规划(State Compressed Dynamic Programming)等含义。“dp++”可能是在特定项目、社区或技术环境中对某种基于“dp”技术的拓展、增强版本的称呼,但由于缺乏具体上下文,难以给出确切含义。 如果“dp”指的是动态规划,“dp++”可能代表对传统动态规划算法的优化改进。例如,在缩动态规划(State Compressed Dynamic Programming)里,核心思想是用整数的二进制表示复杂态集合,每个比特位代表元素的某种态,一个整数可存储多个独立态信息,实现高效的态转移和查询 [^1]。“dp++”或许是在缩基础上进一步优化存储效率或态转移速度的算法。 在代码操作方面,假如传统动态规划使用二维数组`dp[i][j]`来保存态,“dp++”可能会采用更高级的数据结构或算法来减少空间复杂度和时间复杂度。 以下是一个简单的传统动态规划示例(以求解最长公共子序列为例): ```python def longest_common_subsequence(text1, text2): m, n = len(text1), len(text2) dp = [[0] * (n + 1) for _ in range(m + 1)] for i in range(1, m + 1): for j in range(1, n + 1): if text1[i - 1] == text2[j - 1]: dp[i][j] = dp[i - 1][j - 1] + 1 else: dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) return dp[m][n] text1 = "abcde" text2 = "ace" print(longest_common_subsequence(text1, text2)) ``` “dp++”可能会对上述代码进行优化,比如采用滚动数组来减少空间使用。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值