BZOJ1076/Luogu2473 奖励关(SCOI2008)状压DP+期望DP

题意:给n(n<=15)种宝物宝物有价值w且每个宝物有一个前置宝物(即你必须先吃过它的所有前置宝物至少一次才能吃该宝物),共有m轮游戏,每一轮会在n种宝物等概率选一个出来,因为宝物价值可正可负你可以选择吃掉或者不吃,问m轮后你能获得的最大价值。

解法:这道题挺有意思的。看到n<=15容易想到用状压DP,于是我的第一想法是因为

但是此题起点是一定的但是终点不一定,所以从终点往回推可能会简单一些,于是设dp[x][S]代表1~x-1轮的状态为S,x~m轮的最大期望为dp[x][S] 。一定要重点注意这个状态的设计,这样设计状态会使得状态转移方程也比较好写:首先是对于每一个dp[i][j]要枚举k代表在此状态下等概率发的牌是第k种宝物

dp[i][j]+=max(dp[i+1][j|(1<<k-1)]+w[k],dp[i+1][j]);  (k在状态j下能吃,选择吃或不吃)

dp[i][j]+=dp[i+1][j]; (k在状态下不能吃,没得选择,肯定不能吃)

加完之后是期望和,那么dp[i][j]/=n;  代表期望。

初始化dp[m+1][]=0 ,答案就是dp[1][0]。

代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N=17;
int n,m,w[N];
vector<int> G[N];
double dp[110][1<<N];  //dp[x][S]代表1~x-1轮的状态为S,x~m轮的最大期望为dp[x][S] 

bool check(int x,int S) {
    for (int i=0;i<G[x].size();i++) {
        int y=G[x][i];
        if ((S&(1<<(y-1)))==0) return 0;
    }
    return 1;
}

int main()
{
    cin>>m>>n;
    for (int i=1;i<=n;i++) {
        scanf("%d",&w[i]); int t;
        while (scanf("%d",&t) && t) G[i].push_back(t);
    }
    
    for (int i=m;i;i--)
        for (int j=0;j<(1<<n);j++) {
            for (int k=1;k<=n;k++)
                if (check(k,j)) dp[i][j]+=max(dp[i+1][j|(1<<k-1)]+w[k],dp[i+1][j]);
                else dp[i][j]+=dp[i+1][j];
            dp[i][j]/=(double)n;        
        }
    printf("%.6lf
",dp[1][0]);    
    return 0;
}
原文地址:https://www.cnblogs.com/clno1/p/11629615.html