【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;
}

 

 

posted @ 2017-09-27 09:16  GXZlegend  阅读(558)  评论(1编辑  收藏  举报