题解 P2473 [SCOI2008] 奖励关
Ezio__Auditore · · 题解
P2473 [SCOI2008] 奖励关 题解
[Statement]
Date: May 5th 2021
前言
一道比较简单的期望状压 dp 题。
分析:
题意转化后,就是要求你在最优策略下的期望得分。(最优策略是指能最大化期望得分策略)
注意到
我们记录物品
dp 设计:
显然最优策略和当前持有的物品以及轮数有关,可以考虑利用状压 dp 求解。
如果从前往后转移,会发现问题不满足最优子结构(比如选择一个
因此我们无法确定当前的最优策略,不能从前往后 dp。
不过如果我们知道了第
于是我们可以 从后往前 dp,具体的:
- 设计
dp_{i,S} 表示当前还剩i 轮持有物品集合为S 在最优策略下的未来的期望收益。
不难发现这样 dp 没有后效性,满足最优子结构。当前选与不选某一个物品,并不会对更早的轮次产生影响。
dp 转移:
首先枚举剩余轮数
当枚举到第
- 如果
j 的前提集合不被满足,直接爬,反正拿不了,不用讨论,取dp_{i-1, S} 。 -
如果
j 的前提集合被满足,根据p_j 分类:-
若
p_j \ge 0 就直接拿,反正不亏,一定是最优策略,取p_j + dp_{i-1, S | j} 。 -
若
p_j < 0 就去比较还剩i-1 轮时,取\max\{p_j + dp_{i-1, S | j}, dp_{i-1, S} \} ,分别表示:-
选择物品
j ,接受惩罚代价p_j 使下一轮物品集合S' = S | j 。 -
放弃物品
j ,下一轮物品集合仍为S 不变。
-
-
对抛出
综上,可以将转移表示为:
答案为
正确性显然,复杂度
初始化时,令
实现的时候第
实际有用的 dp 数组只有
Code:
#include <bits/stdc++.h>
template<class _Tp = int> _Tp read() {
_Tp w = 0;
bool f = 0;
char ch = getchar();
while (!isdigit(ch)) f |= ch == '-', ch = getchar();
while (isdigit(ch)) w = (w << 3) + (w << 1) + (ch ^ 48), ch = getchar();
return f ? -w : w;
}
const int kMaxN = 15, kMaxK = 1e2 + 5;
int n, k;
int R[kMaxN];
int p[kMaxN];
double dp[kMaxK][1 << kMaxN];
int main() {
k = read(), n = read();
for (int i = 0, r; i < n; i++) {
p[i] = read();
while(r = read())
R[i] |= 1 << (r - 1);
}
for (int i = 1; i <= k; i++)
for (int S = 0; S < 1 << n; S++) {
for (int j = 0; j < n; j++)
if ((R[j] & S) != R[j]) dp[i][S] += dp[i - 1][S];
else
dp[i][S] += std::max(dp[i - 1][S], p[j] + dp[i - 1][S | 1 << j]);
dp[i][S] /= n;
}
printf("%.6lf", dp[k][0]);
return 0;
}