[Luogu P2473] 奖励关

奖励关

题意简述:共有 \(K\) 轮,有 \(n\) 种物品,每一轮出现每一种物品的概率 \(\frac{1}{n}\),物品可选可不选,对于选每一种物品,必须要在前面的轮先选给定的部分物品,每一种物品的价格可正可负。求 \(k\) 轮后按最优方案选择的期望价格。

数据范围:\(1\leq K \leq 100\) , \(1≤n≤15\)

思路

首先看题,概率 \(dp\) 没得跑。

再看数据范围,哦豁!状压 \(dp\)

这道题不同的是需要倒推,因为正推的话有些情况在转移时选择宝物的概率并不是平均的(有宝物合集的限制),这样就会导致结果出现问题,且最终答案的状态表示十分麻烦 不要问我是怎么知道的!,因此选择倒推。

另外,因为 \(dp\) 的转移至于当前局前一局有关,所以可以用滚动数组优化一下。

还有就是,这里用到了一个状压小技巧。很多时候题目会给定条件限制,即枚举状态的前提条件。这里就再次用到了二进制小技巧。

这里就以这道题为例:

	while(~scanf("%d",&x) && x)
		num[i] += 1 << (x - 1);//用二进制数存储 宝物集合 

如果得到第 \(i\) 个宝物之前需要得到第 \(x\) 个宝物,我们就在 \(num[i]\) 的值加上 \(1 << (x - 1)\),最后\(num[i]\)的值就是要得到第 \(i\) 个宝物的先决条件,枚举时的状态必须满足 \(num[i]\) 的要求。

其实这里就是在模拟二进制的形成,计算出满足题意的状态值,是不是很简单啊。

解题步骤

  • 先循环游戏轮数 \(i\)
  • 然后每轮枚举当前宝物的状态 \(j\)(第 \(x\) 位为 \(0\) 则未得到该宝物,反之得到);
  • 再枚举当局游戏抽到的宝物编号 \(k\),判断是否满足条件(满足该宝物的先决条件)在拿 \(k\) 宝物和不拿之间选最大值加到当前状态;
  • 最终期望要乘上随机选择一个宝物的概率,因为是随机选择的啦。
  • 因为是倒推的,所以最后答案为游戏开始时,未得到任何一个宝物的情况

坑:因为用的滚动数组,所以每次记得清空 不要问我是怎么知道的!

其他细节讲解都在代码里啦~

完整代码

#include<cstdio>
#include<algorithm>
using namespace std;
int K,n,x,w[20],num[20];
double p,dp[2][1 << 15];
int main() {
	scanf("%d %d",&K,&n);
	p = 1.0 / n;//选择宝物的平均概率 
	for(int i = 1; i <= n; i ++) {
		scanf("%d",&w[i]);//分值 
		while(~scanf("%d",&x) && x)
			num[i] += 1 << (x - 1);//用二进制数存储 宝物集合 
	}
	int maxn = 1 << n;
	for(int i = K; i >= 1; i --) { //游戏轮数 
		for(int j = 0; j < maxn; j ++) {
			dp[i & 1][j] = 0;//初始化(清空上一次循环的值) 
			for(int k = 1; k <= n; k ++) {//枚举这轮抽到的宝物 
				if((j & num[k]) == num[k])//如果满足k宝物的 宝物集合要求,
					dp[i & 1][j] += max(dp[(i + 1) & 1][j | (1 << (k - 1))] + w[k],dp[(i + 1) & 1][j]);
					//满足最优策略,在拿k宝物和不拿之间选最大值 
				else dp[i & 1][j] += dp[(i + 1) & 1][j];//不满足的话,该状态及为上一次的状态,没有变化 
			}
			dp[i & 1][j] *= p;//最终期望要乘上随机选择一个宝物的概率 
		}
	} 
	printf("%.6lf",dp[1][0]);
	//因为是倒推的,所以最后答案为游戏开始时,未得到任何一个宝物的情况 
	return 0;
} 
posted @ 2021-01-16 16:27  Spring-Araki  阅读(78)  评论(1编辑  收藏  举报