BZOJ 3029 守护者的挑战(DP+概率期望)
打开了黑魔法师Vani的大门,队员们在迷宫般的路上漫无目的地搜寻着关押applepi的监狱的所在地。突然,眼前一道亮光闪过。“我,Nizem,是黑魔法圣殿的守卫者。如果你能通过我的挑战,那么你可以带走黑魔法圣殿的地图……”瞬间,队员们被传送到了一个擂台上,最初身边有一个容量为K的包包。
擂台赛一共有N项挑战,各项挑战依次进行.第i项挑战有一个属性\(a_i\),如果\(a_i>=0\),表示这次挑战成功后可以再获得一个容量为\(a_i\)的包包;如果\(a_i=-1\),则表示这次挑战成功后可以得到一个大小为1 的地图残片。地图残片必须装在包包里才能带出擂台,包包没有必要全部装满,但是队员们必须把 获得的所有的地图残片都带走(没有得到的不用考虑,只需要完成所有N项挑战后背包容量足够容纳地图残片即可),才能拼出完整的地图.并且他们至少要挑战成功L次才能离开擂台。
队员们一筹莫展之时,善良的守卫者Nizem帮忙预估出了每项挑战成功的概率,其中第i项挑战成功的概率为\(p_i\)%。现在,请你帮忙预测一下,队员们能够带上他们获得的地图残片离开擂台的概率。
\(input\)
第一行三个整数N,L,K.
第二行N个实数,第i个实数\(p_i\)表示第i项挑战成功的百分比.
第三行N个整数,第i个整数\(a_i\)表示第i项挑战的属性值.
\(output\)
一个整数,表示所求概率,四舍五入保留6位小数.
\(0<=N<=200,0<=k<=2000,0<=L<=N\)
\(-1<=a_i<=1000,0<=p_i<=100\)
设\(f[i][j][k]\)表示经过前i项挑战,当前容量为j,有k项挑战获得了胜利的概率.
因为最多只有200场挑战,每次挑战最多得到一个大小为1的地图残片,所以最多消耗200的背包容量,即j的最小值为-200,我们最多也只需要200的背包容量,即j的最大值为200,因为数组对负下标的操作不方便,所以不妨把\([-200,200]\)平移到\([0,400]\)来,相当于把200看作0,\(j>200\)表示背包容量有剩余,\(j<200\)表示还有碎片装不下.
转移只有两种,即这次挑战成功/失败.
int N,L,K,a[205];
double ans,win[205],lose[205];
double f[205][405][205];
int main(){
N=read();L=read();K=read();
if(K>200)K=200;
//应该不会有人跟我一样,写成if(k>400)k=400吧
//我们只是把[-200,200](看作)移到[0,400]
//k本身的意义不变,背包容量最大为200就足够了
f[1][K+200][0]=1;//初始化
for(int i=1;i<=N;i++){
double ch;cin>>ch;
win[i]=(double)ch/100.0;
lose[i]=(double)1.0-win[i];
}
//把每一次挑战的胜率和败率分别用两个数组存下
for(int i=1;i<=N;i++){
a[i]=read();
}
for(int i=1;i<=N;i++)
for(int j=0;j<=400;j++)
for(int k=0;k<N;k++){
if(f[i][j][k]==0)continue;
f[i+1][j][k]+=f[i][j][k]*lose[i];
//这是第i次(也就是前i+1次)挑战失败的状态转移
if(a[i]>=0){//表示这次胜利且获得背包容量
int cnt=f[i][j][k]*win[i];//偷懒,后面多次用到
if(j+a[i]>400)//超过400移回来
f[i+1][400][k+1]+=cnt;
else f[i+1][j+a[i]][k+1]+=cnt
}
else f[i+1][j-1][k+1]+=cnt;//表示这次胜利且获得碎片
}
N++;
//我们要知道第n次的结果,也就是要知道前n+1次的结果
for(int j=200;j<=400;j++)
for(int k=L;k<N;k++)
ans+=f[N][j][k];
//j在[200,400]范围内才表示背包容量有剩余
//因为要至少取得L次胜利,最多获得 当前的N - 1 次胜利
printf("%.6lf\n",ans);
return 0;
}