洛谷题单指南-状态压缩动态规划-P2473 [SCOI2008] 奖励关

原题链接:https://www.luogu.com.cn/problem/P2473

题意解读:有n中宝物,每个宝物有得分,有能不能吃的条件(之前吃过哪几种宝物才能吃当前宝物),抛k次,求最高得分的期望。

解题思路:

如果做过https://www.cnblogs.com/hackerchef/p/18944242这道题,那么不难想到,这样的题用记忆化搜索来思考更顺畅。

设从初始节点开始:状态为0,二进制位1表示已经吃过的宝物

对于每一个节点,设dp(cnt, status)表示从抛出第cnt次宝物开始,已吃宝物状态为status时,最终能得到的最高得分期望

答案必须是dp(1, 0)

接下来看如何转移?

对于某一次抛出的宝物,一共有n种可能,通过枚举n个宝物,判断当前状态status是否满足吃该宝物,

如果不能吃,则有dp(cnt, status) = dp(cnt + 1, status)

如果能吃,则可以吃也可以不吃,两种情况取max,则有dp(cnt, status) = max(dp(cnt + 1, status), dp(cnt + 1, status |= (1 << i - 1)))

由于求期望,每一次宝物都有n中可能,因此求出的dp(cnt, status) /= n

状态的表示以及判断当前状态能否吃某个物品,就借助了状态压缩

最后,为了进一步提升递归效率,加上记忆化搜索即可。

100分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 16, K = 105, INF = 1e9;

int k, n;
int p[N], s[N];
double f[K][1 << N];

//从抛第cnt个宝物开始,已获得宝物状态为status,能得到的最多分数的期望
double dp(int cnt, int status)
{
    double res = 0;
    if(cnt > k) return res;
    if(f[cnt][status] > -INF) return f[cnt][status]; //记忆化剪枝
    for(int i = 1; i <= n; i++) //第cnt个宝物有n中可能
    {
        if((status & s[i]) == s[i]) //对应的宝物是否能满足能吃的条件
        {
            res += max(dp(cnt + 1, status), dp(cnt + 1, status | (1 << i - 1)) + p[i]); //满足条件,可以选择吃或不吃,取max
        }
        else res += dp(cnt + 1, status); //不满足能吃的条件
    }
    return f[cnt][status] = res / n; //除以n即计算期望,同时记忆化
}

int main()
{
    cin >> k >> n;
    for(int i = 1; i <= n; i++)
    {
        int x;
        cin >> p[i];
        while(cin >> x && x != 0)
        {
            s[i] |= (1 << x - 1);
        }
    }
    for(int i = 1; i <= k; i++)
        for(int j = 0; j < 1 << n; j++)
            f[i][j] = -INF;
    cout << fixed << setprecision(6) << dp(1, 0);

    return 0;
}

 

posted @ 2025-09-01 16:02  hackerchef  阅读(9)  评论(0)    收藏  举报