题意
已知账户里最多有n元,有一个取款下限,高于可以将钱取走,低于的话会被警告一次。警告次数超过m次时,将会被当做小偷。。。
n,m≤2×1e3.
题解
比赛时,没有意识到是一道DP题,一直在二分的角度想题。还是题目理解的不够深刻。下面给个数学期望的定义吧。
数学期望(mean)(或均值,亦简称期望)是试验中每次可能结果的概率乘以其结果的总和。–出自百度百科
看了很多的博文,都只是给出了DP。看到nuonuo_orz的博客的时候,才恍然大悟。也不得不佩服作者。
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。
AC代码
#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <queue>
#include <stack>
#include <string>
#include <bitset>
#include <cstdio>
#include <limits>
#include <vector>
#include <climits>
#include <cstring>
#include <cstdlib>
#include <fstream>
#include <numeric>
#include <sstream>
#include <iostream>
#include <algorithm>
#define MEM(a,x) memset(a,x,sizeof(a))
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
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);
}
}
}
int k,w;
while(~scanf("%d%d",&k,&w)){
printf("%.6f\n",dp[k][min(w,11)]);
}
return 0;
}