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