【机房练习赛 5.5】 atm 自动取款机
题面
题目描述
小沈阳在小品里说过:“人生最痛苦的事情是人死了,钱还没花了”。
于是小宋(80 岁)决定要将所有的储蓄从ATM 机中取出花光。小宋忘记了她有多少存款
(银行卡密码她是记得的2333),这个奇怪的ATM 不支持查询存款余额功能。小宋知道她
存款的唯一信息是存款上限是 \(K\)元,这意味着小宋的存款 \(x\) 是 \(0\) 到 \(K\) 之间的随机整数(包括 \(K\))。
每次小宋都可以尝试从ATM 中拿出一些钱。如果她要取的y 元钱不大于她的存款,ATM 将立即给小宋 \(y\) 元。但如果她的存款小于 \(y\),小宋将收到ATM 的警告。
如果小宋被警告超过 \(w\) 次,那么她将被警方带走,作为小偷。
小宋希望取钱次数期望最小。
由于小宋聪明,她总是采取最好的策略。
请计算小宋将所有储蓄从自动取款机中取出期望次数最小值是多少,并不得被警方带走。
输入样例
每个测试点包含多组测试数据(最多\(10\) 组)
每组测试数据包含两个整数 \(K\),\(W\)。
\(1 \leq K,W \leq 2000\)
1 1
4 2
20 3
输出样例
对于每组测试数据输出取钱次数最小的期望值,舍入到小数点后 \(6\) 位。
1.000000
2.400000
4.523810
然而她并不知道赵本山说了:“人生最最痛苦的事情是人活着呢钱没了”。。。
这是个悲伤的故事。
题解
虽然是道比较基础的期望 \(dp\),但是对于 \(dp\) 很菜的我,只能傻傻的模拟手算打了个二分,快乐地爆了零。。。
状态
设 \(f[i][j]\) 表示存款的上限为 \(i\),还可以被警告 \(j\) 次的取钱次数的期望。
阶段
当前的上限,还可以被警告的次数,此次取的钱的数量。
状态转移方程
写了博客之后终于搞懂了!!!
- 前一项表示没有猜大。因为没有猜大的话,就会从中取出 \(p\) 元,且不会被警告,所以之后还要在上限为 \(i - p\) 的存款中继续取,所以前一项的状态是 \(f[i -p][j]\),根据期望的性质,还要再乘上概率 \(\frac{i - p + 1}{i + 1}\),加一是因为包含 \(0\),表示期望在剩余的上限为 \(i - p\) 的钱里继续取。
- 后一项表示猜大了。由此就可以确定剩余钱数的范围在 \(0\) ~ \(p- 1\) 的范围内,但会被警告一次,所以之后在上限为 \(p - 1\) 的钱数里继续取,但只剩下 \(j - 1\) 次机会,再乘上概率 \(\frac{p}{i +1}\)。
初始化
优化
因为小宋很聪明,他不会傻傻的跑一个 \(O(k^2w)\),所以他会每次二分的选,所以最多被警告 \(O(logk)\) 次,所以超过 \(log(k)\) 次的期望其实都是一样的,所以总时间复杂度是 \(O(k^2logk + t)\) 。
代码如下
注意多测!可以直接预处理出所有的答案,然后 \(O(1)\) 回答。
#include<cstdio>
#include<iostream>
using std::min;
const int N = 2e3 + 5;
int k,m;
double f[N][12];
int main() {
freopen("atm.in","r",stdin);
freopen("atm.out","w",stdout);
for(int i = 0; i < N; i++) f[i][0] = 1e9;
for(int i = 0; i <= 11; i++) f[0][i] = 0;
for(int i = 1; i < N; i++)
for(int j = 1; j <= 11; j++) {
f[i][j] = 1e9;
for(int p = 1; p <= i; p++)
f[i][j] = min(f[i][j],1.0 * (i - p + 1) / (i + 1) * f[i - p][j] + 1.0 * p / (i + 1) * f[p - 1][j - 1] + 1);
}
while(~scanf("%d%d",&k,&m))
printf("%.6lf\n",f[k][min(m,11)]);
return 0;
}