四川2008省选·洛谷·奖励关

37 篇文章 0 订阅
3 篇文章 0 订阅

初见安~这里是传送门:洛谷P2473

题目描述

你正在玩你最喜欢的电子游戏,并且刚刚进入一个奖励关。在这个奖励关里,系统将依次随机抛出k次宝物,每次你都可以选择吃或者不吃(必须在抛出下一个宝物之前做出选择,且现在决定不吃的宝物以后也不能再吃)。

宝物一共有n种,系统每次抛出这n种宝物的概率都相同且相互独立。也就是说,即使前k-1 次系统都抛出宝物1(这种情况是有可能出现的,尽管概率非常小),第k次抛出各个宝物的概率依然均为1/n。

获取第 i 种宝物将得到Pi分,但并不是每种宝物都是可以随意获取的。第i种宝物有一个前提宝物集合Si。只有当Si中所有宝物都至少吃过一次,才能吃第i 种宝物(如果系统抛出了一个目前不能吃的宝物,相当于白白的损失了一次机会)。注意,Pi 可以是负数,但如果它是很多高分宝物的前提,损失短期利益而吃掉这个负分宝物将获得更大的长期利益。

假设你采取最优策略,平均情况你一共能在奖励关得到多少分值?

输入格式:

第一行为两个正整数k 和n,即宝物的数量和种类。以下n行分别描述一种

宝物,其中第一个整数代表分值,随后的整数依次代表该宝物的各个前提宝物(各

宝物编号为1到n),以0结尾。

输出格式:

输出一个实数,保留六位小数,即在最优策略下平均情况的得分。

输入样例#1:

1 2
1 0
2 0

输出样例#1:

1.500000

输入样例#2:

6 6
12 2 3 4 5 0
15 5 0
-2 2 4 5 0
-11 2 5 0
5 0
1 2 4 5 0

输出样例#2:

10.023470

说明

1 <= k <= 100, 1 <= n <= 15,分值为[-106,106]内的整数。

 

题解

(思路其实和其他题解应该差不多,主要是一些更详细的理解)

首先有前提条件了,如果我们一个一个枚举的话又会很难存储状态,所以我们想到了状态压缩

但是(亲身经历)如果我们以dp[ i ] [ j ]作为前i次抛出后在j状态下的话,我们会发现要多很多合法判定——j中设计的种类是否在i以内,枚举的前一个状态是否在i - 1以内,两个状态有没有冲突……很麻烦,而且总改不对。所以我们就熬考虑逆向思考——作为一个概率平均的期望题,可以想到求概率的时候有一个小技巧——正难则反。这里虽然不尽相同,但我们可以尝试一下——以dp[ i ] [ j ]作为前i - 1个物品后到达j状态,存i ~ k次抛出的最大期望。dp取max天然保证最大。也就是说,我们的最外层i的for循环将倒过来进行,这样就可以天然保证j状态是存在的,我们只考虑是否满足条件即可。而在我们使用状态压缩的前提下,比较条件就简单很多了,直接二进制操作,相信你也想得到:)

先上代码和详解——

#include<bits/stdc++.h>
#define maxn  105
using namespace std;
int n, K, v[maxn], cndtn[maxn];
double dp[maxn][1 << 18];
int main()
{
	scanf("%d%d", &K, &n);
	
	for(int i = 1; i <= n; i++)
	{
		int temp = 1, c = 0;
		scanf("%d", &v[i]);
		while(temp)//二进制表示
		{
			scanf("%d", &temp);
			if(!temp) break;
			c += 1 << (temp - 1);//用|也可以。
		}
		cndtn[i] = c;
	}
	
	for(int i = K; i; i--)//逆向操作
		for(int j = 0; j < (1 << n); j++)
		{
			for(int k = 1; k <= n; k++)//枚举物种
			{
				if((j & cndtn[k]) == cndtn[k])//该有的条件都有了
					dp[i][j] += max(dp[i + 1][j], dp[i + 1][j | (1 << k - 1)] + v[k]);
                    //可要可不要
				else dp[i][j] += dp[i + 1][j];
			}	
				dp[i][j] /= n;//这里就要乘概率1/n,因为求的相当于同时发生的概率,要累乘
		}
	printf("%lf", dp[1][0]);
	return 0;
}

//一份真的是简洁的过分的紫题代码

不知道你会不会有类似的问题——就是

dp[i][j] += max(dp[i + 1][j], dp[i + 1][j | (1 << k - 1)] + v[k]);

这一步中,j | (1 << k - 1)的话相当于一定找一个已经得到过k的状态进行转移,为什么不能从没有得到过k的状态转移过来呢?因为我们倒过来思考了。既然倒过来了,那么就一定不可能存在现在我得到了但是后来的状态中却显示没有得到,所以一定是从已经得到了的状态推进回来。逆向思考虽然可以省很多代码和细节问题的处理,但是思考的过程中也会有很多需要或者不需要跟随逆向思考而倒转的操作,所以这道题——难度还是不小。

 

迎评:)
——End——

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值