题意:给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; }