题意:Alice想把自己的存款全部取出,但是她忘记了自己的具体存款,只记得自己的存款是在[0,K]范围内的整数,同时ATM机出了毛病,不会显示存款数。当你想从里面取出不超过你存款的钱数时,它会出钞,如果想取的钱大于存款数的话,它不会出钞,还会警告你一次,当你的警告次数超过了W次时,警察会把你带走。问Alice取出所有存款且不会被警察带走的期望数。(1<=K,W<=2000)
思路:
dp[i][k]代表已知存款在[0,i]范围内,还有k次警告次数时的期望值。设j为下次的取款数,有两种情况:
- 如果实际存款数低于这个j的话,问题就退化成了求dp[j-1][k-1]的值
- 如果实际存款数高于或等于这个j的话,问题就退化成了求dp[i-j][k]的值(此时把范围确定到了[j,i],等价于把范围确定到了[0,i-j])
求期望,就是要先求所有可能情况,然后取平均不是吗?存款范围在[0,i]的话,那么存款可以是[0,i]内的任意一个数,当存款是[0,j]时,期望就取决于dp[j-1][k-1],当存款是[j+1,i]的时候,期望就取决于dp[i-j][k],因此状态转移方程是:dp[i][k]=min(dp[i][k],(j*dp[j-1][k-1]+(i-j+1)*dp[i-j][k])/(i+1)+1)。
考虑到K最大只有2000,不计警告次数的话,采用二分策略,也只要11次二分就ok。所以W超过11后,对答案就不会有影响。
边界情况:当存款范围是[0,0]的时候,不管警告次数,答案都是0。
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<cstdlib>
using namespace std;
#define MS(x,y) memset(x,y,sizeof(x))
#define MP(x,y) make_pair(x,y)
#define lowbit(x) (x&(-x))
typedef long long LL;
inline void fre1(){freopen("input.txt","r",stdin);/*freopen("output.txt","w",stdout);*/}
inline void fre2(){fclose(stdin);/*fclose(stdout);*/}
const int MAXN=2000+5;
const double EPS=1e-8;
double dp[MAXN][16];
bool vis[MAXN][16];
int main()
{
for(int i=0;i<MAXN;++i){
for(int j=0;j<16;++j)
dp[i][j]=1e12;
}
for(int i=0;i<16;++i) dp[0][i]=0;
for(int i=1;i<=2000;++i){
for(int j=1;j<=i;++j){
for(int k=1;k<=15;++k){
dp[i][k]=min(dp[i][k],(j*dp[j-1][k-1]+(i-j+1)*dp[i-j][k])/(i+1)+1);
}
}
}
// for(int i=0;i<15;++i){
// for(int j=0;j<15;++j){
// printf("%12.6f",dp[i][j]);
// }
// putchar(10);
// }
int k,w;
while(~scanf("%d%d",&k,&w)){
printf("%.6f\n",dp[k][min(w,11)]);
}
return 0;
}
其它:
因为多校二的时候做了一道叫Permutation Bo的题,自己就以为求概率这种东西只要找到策略,确定不同情况对答案的贡献,就可以出解了。果然自己还是too young too navie,不过遇到了这种题,也是学习了。
题目里用dp[i]表示存款在[0,i]范围内的情况,其实就是表示[k,i+k](k为任意自然数)的情况,这种表示方法利用了题目的特殊性,节省了空间,提高了效率。