【bzoj3029】守卫者的挑战 概率dp
题目描述
给出一个数$m$和$n$次操作,第$i$操作有$p_i$的概率成功,成功后会使$m$加上$a_i$($a_i$为正整数或$-1$),求$n$次操作以后成功的操作次数不少于$l$且$m\ge 0$的概率。
输入
第一行三个整数N,L,M。
第二行N个实数,第i个实数pi表示第i项挑战成功的百分比。
第三行N个整数,第i个整数ai表示第i项挑战的属性值.
输出
一个整数,表示所求概率,四舍五入保留6 位小数。
样例输入
3 1 0
10 20 30
-1 -1 2
样例输出
0.300000
题解
概率dp
设$f[i][j][k]$表示前$i$次操作成功了$j$次,此时$m$的值为$k$的概率。
那么状态转移显然。
然而有一个问题:$m(k)$的范围过大。
考虑到$a_i$仅为正整数或$-1$,而最终只要求$m\ge 0$。当一个时刻$m\ge n$时,无论怎么减少都不会降到$0$以下。因此当$m>n$时直接将其看作$n$处理即可。
数组下标需要向右平移$n$位。
由于空间不足需要使用滚动数组。
时间复杂度$O(n^3)$
#include <cstdio> #include <cstring> #include <algorithm> #define N 210 using namespace std; double p[N] , f[2][N][N << 1]; int main() { int n , t , m , a , i , j , k , d; double ans = 0; scanf("%d%d%d" , &n , &t , &m) , m = min(m , n); for(i = 1 ; i <= n ; i ++ ) scanf("%lf" , &p[i]) , p[i] /= 100; f[0][0][n + m] = 1; for(d = i = 1 ; i <= n ; i ++ , d ^= 1) { scanf("%d" , &a); for(j = 0 ; j <= n ; j ++ ) for(k = 0 ; k <= n * 2 ; k ++ ) f[d][j][k] = f[d ^ 1][j][k] * (1 - p[i]); for(j = 0 ; j < n ; j ++ ) for(k = 1 ; k <= n * 2 ; k ++ ) f[d][j + 1][min(k + a , n * 2)] += f[d ^ 1][j][k] * p[i]; } for(i = t ; i <= n ; i ++ ) for(j = n ; j <= 2 * n ; j ++ ) ans += f[n & 1][i][j]; printf("%.6lf\n" , ans); return 0; }