【数学,DP】AcWing 232. 守卫者的挑战
这题看起来不难然而一堆细节。。
分析
首先不难看出这是一个类似于背包的 dp 问题。
考虑状态的设计:\(f(i, j, k)\) 表示当前考虑到第 \(i\) 个挑战,当前背包剩余容量为 \(j\),前 \(i\) 个挑战中已经成功了 \(k\) 个。
那么我们可以进一步写出转移方程:
- 第 \(i\) 个挑战失败时:\(f(i-1, j, k) \to f(i, j, k)\)
- 第 \(i\) 个挑战成功时:\(f(i-1, j, k) \to f(i, j+w_i, k+1)\)
接下来就是细节问题了:
约定 \(V\) 为地图残片的数量。
- 首先,注意到 \(\sum w_i\) 会非常大,直接开数组自然不行,事实上当背包容量 \(\geq V\) 的时候能够保证装下所有的地图残片,因此我们可以对状态表示进行调整:对于 \(f(i, V, k)\) 表示当前背包剩余容量 \(\geq V\),\(i, k\) 定义不变。
- 其次,因为我们是对挑战直接进行 \(1\to n\) 的扫描,有可能在过程中出现背包容量为负的情况,但是我们不能直接舍弃这类状态,因为有可能在未来这样的状态可以转移到背包容量为正的情况,那么我们需要想办法将这类状态也保存,具体做法是:将 \(j\) 整体加上一个 \(V\) 的偏移量(类似于将数轴平移)。
// Problem: 守卫者的挑战
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/234/
// Memory Limit: 256 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include<bits/stdc++.h>
using namespace std;
#define debug(x) cerr << #x << ": " << (x) << endl
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define dwn(i,a,b) for(int i=(a);i>=(b);i--)
#define pb push_back
#define all(x) (x).begin(), (x).end()
inline void read(int &x){
int s=0; x=1;
char ch=getchar();
while(ch<'0' || ch>'9') {if(ch=='-')x=-1;ch=getchar();}
while(ch>='0' && ch<='9') s=(s<<3)+(s<<1)+ch-'0',ch=getchar();
x*=s;
}
const int N=220;
int n, L, K, V;
double p[N];
int w[N];
int main(){
cin>>n>>L>>K;
rep(i,1,n) cin>>p[i], p[i]/=100;
rep(i,1,n){
read(w[i]);
if(w[i]==-1) V++;
}
K=min(V, K);
double f[n+1][V<<1|1][n+1];
memset(f, 0, sizeof f);
f[0][V+K][0]=1;
rep(i,1,n){
rep(j,0,V<<1) rep(k,0,n){
f[i][j][k]+=(1.0-p[i])*f[i-1][j][k];
if(j+w[i]>=0 && k<n) f[i][min(j+w[i], V<<1)][k+1]+=p[i]*f[i-1][j][k];
}
}
double res=0;
rep(i,V,V<<1) rep(j,L,n) res+=f[n][i][j];
printf("%.6lf\n", res);
return 0;
}