传送门:点击打开链接
题意:
奖励关中,系统依次随机抛出k次宝物,每次选择吃或者不吃。宝物共n种,系统每次抛出这n种宝物的概率都相同且相互独立。 获取第i种宝物将得到Pi分,第i种宝物有一个前提宝物集合Si。只有当Si中所有宝物都至少吃过一次,才能吃第i种宝物。Pi可以是负数。假设采取最优策略,平均情况一共能在奖励关得到多少分值?
数据规模:
1<=k<=100,1<=n<=15(划重点),分值为【-1e6,1e6】内的整数。
题解:
数据量这么小,直接选择状压dp,以每一种宝物的2^i位来dp;利用二进制下的位或位与。
一般来说,概率顺推,期望倒推。因为概率要保证前一次出现存在这种情况来推算,而期望顺推难以判断前提是否成立。拿这道题来说,当第t种宝物落下时,顺推无法确定是否满足前提,也无法确定是否该吃负分的宝物,那么就来倒推一波~
得状态转移方程f[i][j](第i次下落已吃某几种宝物状态的最优解,某几种又j的二进制表示不为0的位数)
如可以吃 f[i][j]+=max(f[i+1][j],f[i+1][j|p[k]]+v[k]) (假设吃掉第k种宝物,v[k]为分值,p[k]二进制下只有第k位为1)
如不能吃 f[i][j]+=f[i+1][j];
因为求的是平均情况,最后除以一个n就好了。倒推得到f[1][0]即为答案。
代码如下
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
double f[16][65537];
int k,n,p[17],d[17],v[17];
int main(){
scanf("%d%d",&k,&n);
for(int i=1;i<=k+1;i++) p[i]=1<<(i-1);
for(int e,i=1;i<=n;i++){
scanf("%d%d",&v[i],&e);
while(e!=0){
d[i]+=p[e];
scanf("%d",&e);
}
}
for(int i=n;i>=1;i--){
for(int j=0;j<p[k+1];j++){
for(int t=1;t<=n;t++)
if((d[t]&j)==d[t]){
f[i][j]+=max(f[i+1][j],f[i+1][j|p[t]]+v[t]);
}
else f[i][j]+=f[i+1][j];
f[i][j]/=k;
}
}
printf("%.6lf\n",f[1][0]);
return 0;
}