【机房练习赛 5.5】 atm 自动取款机

1 篇文章 0 订阅

题面

题目描述

小沈阳在小品里说过:“人生最痛苦的事情是人死了,钱还没花了”。
于是小宋(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 1K,W2000

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+1ip+1×f[ip][j]+i+1p×f[p1][j1]

  • 前一项表示没有猜大。因为没有猜大的话,就会从中取出 p p p 元,且不会被警告,所以之后还要在上限为 i − p i - p ip 的存款中继续取,所以前一项的状态是 f [ i − p ] [ j ] f[i -p][j] f[ip][j],根据期望的性质,还要再乘上概率 i − p + 1 i + 1 \frac{i - p + 1}{i + 1} i+1ip+1,加一是因为包含 0 0 0,表示期望在剩余的上限为 i − p i - p ip 的钱里继续取。
  • 后一项表示猜大了。由此就可以确定剩余钱数的范围在 0 0 0 ~ p − 1 p- 1 p1 的范围内,但会被警告一次,所以之后在上限为 p − 1 p - 1 p1 的钱数里继续取,但只剩下 j − 1 j - 1 j1 次机会,再乘上概率 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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值