洛谷 P2473 [SCOI2008] 奖励关
题目传送门
思路
确定算法
- 首先,一个物品能不能选,还要看有没有选前提物品;
- 其次,物品种类很少,只有 n ≤ 15 n \leq 15 n≤15;
- 因此,我们可以确定用 状压 d p dp dp 来求期望。
状态设计
- 首先,肯定要有记录当前是第 i i i 轮的一维;
- 其次,由于拿物品还要看已有物品集合,所以要有记录【当前已经拿了物品的集合】的一维;
- 设 d p i , s dp_{i, s} dpi,s 表示在 【第 1 1 1 到 i − 1 i - 1 i−1 轮】 所拿到的物品集合为 s s s 时,第 i i i 到 k k k 轮在最优情况下,所拿物品分值总和的期望。
(为什么这么设计到状态转移会说)
状态转移
采用倒推(为什么不用正推等会说):
- 若目前状态 s s s 不能拿物品 k k k,那么: d p i , s + = d p i + 1 , s n dp_{i, s} += \frac{dp_{i + 1, s}}{n} dpi,s+=ndpi+1,s
- 若目前状态 s s s 可以拿物品 k k k,那么: d p i , s + = m a x ( d p i + 1 , s ∣ ( 1 < < ( k − 1 ) ) + p k , d p i + 1 , s ) n dp_{i, s} += \frac{max(dp_{i + 1, s | (1 << (k - 1))} + p_k, dp_{i + 1, s})}{n} dpi,s+=nmax(dpi+1,s∣(1<<(k−1))+pk,dpi+1,s)分别对应选 k k k 与不选 k k k。
问题一:为什么状态这么设计呢?
- 为了适应倒推的转移方程;
- 如果状态设计为【 d p i , s dp_{i, s} dpi,s 表示到第 i i i 轮拿完后,且状态为 s s s 时,已有的分数的期望】(适应正推),就会影响后面物品的选择。因为有前驱物品集合的限制,这样 d p dp dp 就会有后效性,导致不正确。
问题二:为什么采用倒推呢 ?
- 同样也是因为正推会有后效性。
边界条件
- 对于 ∀ s ∈ [ 0 , 2 n − 1 ] \forall s \in [0, 2^n - 1] ∀s∈[0,2n−1], d p n + 1 , s dp_{n + 1, s} dpn+1,s 均为 0 0 0。
- 含义就是:在 1 1 1 到 n n n 轮选完的状态 s s s 下,已经结束了,不能再选了,因此期望为 0 0 0。
答案
- 就是 d p 1 , 0 dp_{1, 0} dp1,0,含义就是:还没有开始时,一个物品都没选的状态,所能得到的期望分。
复杂度
- 空间: O ( k × n ) O(k \times n) O(k×n);
- 时间: O ( k × n × 2 n ) O(k \times n \times 2^n) O(k×n×2n)。
代码
#include <bits/stdc++.h>using namespace std;const int maxn = 1e2 + 7;
const int maxs = (1 << 15) + 7;int n, m;
struct Prize {int p, s;} a[maxn];
double dp[maxn][maxs];
int main () {scanf("%d%d", &n, &m);for (int i = 1, x; i <= m; ++i) {scanf("%d", &a[i].p);while (1) {scanf("%d", &x);if (!x) break;a[i].s |= 1 << (x - 1);}}for (int i = n; i >= 1; --i) {for (int s = 0; s < (1 << m); ++s) {for (int k = 1; k <= m; ++k) {if ((s & a[k].s) != a[k].s) dp[i][s] += dp[i + 1][s];else dp[i][s] += max(dp[i + 1][s], dp[i + 1][s | (1 << (k - 1))] + a[k].p);}dp[i][s] /= m;}}printf("%.6lf\n", dp[1][0]);return 0;
}
反思
- 依旧只想出了状态,没对转移方程;
- 发现对这种 最优值期望 的转移方程我总会写成 最优值 的转移方程,并没有很好地理解期望的含义及计算方法;
- 运用 倒推 的意识少;
- 那种【前面选择】影响【后面选择】的 d p dp dp 最好用倒推,因为先考虑后面,就不用管前面的影响了。