题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=5781
【题意】给定一个k存款值上限,w表示警告次数上限,实际存款是0-k的任意数,每次询问会回答警告(>)或取款(≤),需要在出现w次警告以内知道实际存款。询问对于已知的k,w知道实际存款需要询问次数的期望。
【分析】比赛以为是固定策略,二分、三分死活做不出来ORZ。实际是不采用固定策略只选取最优策略=-=。用一个dp[i][j]表示存款上限是i,剩余警告次数j的期望。那么假如取k元,成功则剩余i-k,其期望为dp[i-k][j],不成功则剩余k-1,其期望为dp[k-1][j-1]。所以dp[i][j]的期望为dp[i-k][j]*(i-k+1)/(i+1)+dp[k-1][j-1]*k/(i+1)+1,遍历dp[i][j]取最小值即可。采用二分法可以发现k=2000时,最大警告次数是15,及实际上w≥15,效用和w=15是一样的,预处理w=min(15,w)。对于警告次数只剩1次的时候,避免被抓显然智能每次取1,这样只要被警告就说明取完了。所以dp[i][1]的期望是(1+i)*i/2+i)/(i+1)。
【代码】
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int INF=1e9+7;
double dp[2020][20];
int main(){
for(int i=1;i<=2000;++i){
dp[i][1]=(i*(i+1)/2+i)*1.0/(i+1);
for(int j=2;j<=15;++j){
dp[i][j]=INF;
for(int k=1;k<=i;++k)
dp[i][j]=min(dp[i][j],((i-k+1)*dp[i-k][j]/(i+1))+(k*dp[k-1][j-1]/(i+1))+1);
}
}
int k,w;
while(cin>>k>>w){
w=min(15,w);
printf("%.06lf\n",dp[k][w]);
}
}