1. 程式人生 > >bzoj1076 [SCOI2008]獎勵關 狀壓+期望dp

bzoj1076 [SCOI2008]獎勵關 狀壓+期望dp

題意就不說了。

分析:表示我狀壓本來就不好,加上個我不擅長的期望就徹底懵逼了。。
一開始想到把物品選或不選的方案設為狀態,設f[i][j]表示i輪後物品的狀態為j。
然後。。然後我就懵逼了。按照正常套路來說,先列舉輪(從後往前好處理),然後列舉當前狀態,然後再列舉上一輪的狀態看上一輪的狀態是否有選當前所要選的點,但是這樣複雜度直接爆炸。。O((2<<15)^2*15*100)。。

後來看了一波題解,表示學習了。。事實上並不需要每次列舉上一次的選擇物品集合,這個東西是可以預處理的。把第i個物品的那個集合直接化成狀態(就每個都選),然後dp的時候並不需要列舉上一輪的狀態,直接列舉當前狀態然後看s&pre[i]==pre[i](看pre[i]中的所有1是不是在s中也是1).如果可以就直接轉移咯,看是不選得分多還是選了得分多。如果不符合那麼就不能選,直接加上上一輪的值。因為是期望所以每次加的時候除以一個n。

被運算子的優先順序坑了一波,原來位運算的優先順序比四則運算還低。。。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
int n,k;
const int N=1e5;
int a[N],pre[N];
double f[110][1
<<15]; int main() { scanf("%d%d",&k,&n); fo(i,1,n) { scanf("%d",&a[i]); int x; while (scanf("%d",&x),x) pre[i]|=1<<x-1; } fd(i,k,1) fo(j,0,(1<<n)-1) fo(x,1,n) { if ((j&pre[x])==pre[x]) f[i][j]+=max(f[i+1
][j|(1<<x-1)]+a[x],f[i+1][j])/n; else f[i][j]+=f[i+1][j]/n; } printf("%.6lf\n",f[1][0]); return 0; }