洛谷 P2473 [SCOI2008] 奖励关

题目传送门


思路

确定算法

  • 首先,一个物品能不能选,还要看有没有选前提物品;
  • 其次,物品种类很少,只有 \(n \leq 15\)
  • 因此,我们可以确定用 状压 \(dp\) 来求期望。

状态设计

  • 首先,肯定要有记录当前是第 \(i\) 轮的一维;
  • 其次,由于拿物品还要看已有物品集合,所以要有记录【当前已经拿了物品的集合】的一维;
  • \(dp_{i, s}\) 表示在 【第 \(1\)\(i - 1\) 轮】 所拿到的物品集合为 \(s\) 时,第\(i\)\(k\) 轮在最优情况下,所拿物品分值总和的期望。
    (为什么这么设计到状态转移会说)

状态转移

采用倒推(为什么不用正推等会说):

  1. 若目前状态 \(s\) 不能拿物品 \(k\),那么:$$dp_{i, s} += \frac{dp_{i + 1, s}}{n}$$
  2. 若目前状态 \(s\) 可以拿物品 \(k\),那么:$$dp_{i, s} += \frac{max(dp_{i + 1, s | (1 << (k - 1))} + p_k, dp_{i + 1, s})}{n}$$分别对应选 \(k\) 与不选 \(k\)

问题一:为什么状态这么设计呢?

  1. 为了适应倒推的转移方程;
  2. 如果状态设计为【\(dp_{i, s}\) 表示到第 \(i\)拿完后,且状态为 \(s\) 时,已有的分数的期望】(适应正推),就会影响后面物品的选择。因为有前驱物品集合的限制,这样 \(dp\) 就会有后效性,导致不正确。

问题二:为什么采用倒推呢 ?

  • 同样也是因为正推会有后效性。

边界条件

  • 对于 \(\forall s \in [0, 2^n - 1]\)\(dp_{n + 1, s}\) 均为 \(0\)
  • 含义就是:在 \(1\)\(n\) 轮选完的状态 \(s\) 下,已经结束了,不能再选了,因此期望为 \(0\)

答案

  • 就是 \(dp_{1, 0}\),含义就是:还没有开始时,一个物品都没选的状态,所能得到的期望分。

复杂度

  • 空间:\(O(k \times n)\)
  • 时间:\(O(k \times n \times 2^n)\)

代码

#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;
}

反思

  1. 依旧只想出了状态,没对转移方程;
  2. 发现对这种 最优值期望 的转移方程我总会写成 最优值 的转移方程,并没有很好地理解期望的含义及计算方法;
  3. 运用 倒推 的意识少;
  4. 那种【前面选择】影响【后面选择】的 \(dp\) 最好用倒推,因为先考虑后面,就不用管前面的影响了。
posted @ 2025-05-04 16:25  syzyc  阅读(34)  评论(0)    收藏  举报