洛谷传送门
题目描述
你正在玩你最喜欢的电子游戏,并且刚刚进入一个奖励关。在这个奖励关里,系统将依次随机抛出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
1
<=
k
<=
100
,
1
<=
n
<=
15
,分值为[-106,106]内的整数。
解题分析
看到 n<=15 n <= 15 就知道大概是状压DP了, 但正向DP( dp[i][j] d p [ i ] [ j ] 表示第i轮状态j的最大值)似乎会出锅, 因为我们并不知道能不能转移到状态j。
所以我们倒推, 设 dp[i][j] d p [ i ] [ j ] 表示推到第i轮, 第1至i-1轮得到的状态为j, 就可以保证每个状态都可以到达了(因为即使无法到达也对最终状态 dp[1][0] d p [ 1 ] [ 0 ] 没有影响)。
状态转移方程为:
1.如果状态j允许k加入, 即
dp[i][j]+=max(dp[i+1][j],dp[i+1][j|(1<<k−1)]+valk)
d
p
[
i
]
[
j
]
+
=
m
a
x
(
d
p
[
i
+
1
]
[
j
]
,
d
p
[
i
+
1
]
[
j
|
(
1
<<
k
−
1
)
]
+
v
a
l
k
)
。
2.如果状态j不允许k加入, 即
dp[i][j]+=dp[i+1][j]
d
p
[
i
]
[
j
]
+
=
d
p
[
i
+
1
]
[
j
]
因为是求期望, 所以每轮下来 dp[i][j] d p [ i ] [ j ] 都应该除以n。最后 dp[1][0] d p [ 1 ] [ 0 ] 即为答案。
代码如下:
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <cmath>
#include <algorithm>
#define R register
#define W while
using namespace std;
int req[20];
double dp[105][1 << 16];
int val[20];
int kind, tim;
int main(void)
{
int a;
scanf("%d%d", &tim, &kind);
for (R int i = 1; i <= kind; ++i)
{
scanf("%d", &val[i]);
W (233)
{
scanf("%d", &a);
if(!a) break;
req[i] ^= (1 << a - 1);
}
}
int lim = (1 << kind) - 1;
for (R int i = tim; i >= 1; --i)
{
for (R int j = 0; j <= lim; ++j)
{
for (R int k = 1; k <= kind; ++k)
if((j & req[k]) == req[k])
dp[i][j] += max(dp[i + 1][j], dp[i + 1][j | (1 << k - 1)] + val[k]);
else dp[i][j] += dp[i + 1][j];
dp[i][j] /= kind;
}
}
printf("%.6lf", dp[1][0]);
return 0;
}