ZLOJ练习51A & HDU - 5781 ATM Machine
written on 2022-07-27
概率与期望的题目,尤其是设置了情境的好像是模拟题的这类题,应当优先考虑 dp。
往 dp 的方向上想,首先考虑状态的设计。题目中有几个变量:
-
钱的上限。
-
当前的警告次数。
好像就这两个,每次取的钱数可以在转移的时候体现出来。
那么我们可以设 \(f_{i,j}\) 表示当前钱的上限为 \(i\),被警告次数为 \(j\) 的最小期望次数。
然后考虑边界,显然我们有 \(f_{0,0}=1\),然后的话其他的全部设为无穷大,由于是浮点数,所以尽量别用 memset
。
接着考虑转移,对于每一个状态,枚举每次选的钱数 \(k\)。那么对于 \(f_{i,j}\),有两种决策的可能性:
- 实际钱数小于 \(k\)。
此时转移为 \(f_{i,j}=f_{k-1,j-1}\times ...+1\)
- 实际钱数大于等于 \(k\)。
此时转移为 \(f_{i,j}=f_{i-k,j}\times ...+1\)
整合一下两种情况,由于钱数等概率,因此乘上的值只要乘以对应的比率即可,这样的转移就显然了。
额外的问题:即使是预处理出 dp 数组,时间复杂度也达到了 \(O(k^2\times w)\) 的级别,需要优化。
此时我们考虑其现实意义,只要每次取其中点,可以发现最多被警告 \(\log w\) 次即可,因此我们将 \(w\) 与 \(13(\log w)\) 取 \(\min\),这样时间复杂度就是 \(O(k^2\times \log w)\),能过。
Code
#include<bits/stdc++.h>
using namespace std;
double f[2005][13];
int main()
{
for(int i=1;i<=2000;i++) for(int j=0;j<=12;j++) f[i][j]=1e9;
for(int i=1;i<=12;i++) f[0][i]=0;
for(int i=1;i<=2000;i++)
for(int k=1;k<=12;k++)
for(int j=1;j<=i;j++)
f[i][k]=min(f[i][k],f[j-1][k-1]*j*1.0/(i+1)+f[i-j][k]*(i-j+1)*1.0/(i+1)+1);
int K,W;
while(scanf("%d%d",&K,&W)!=EOF) printf("%.6lf\n",f[K][min(W,12)]);
}
PS:个人感觉这题略有些问题,为了保证不超过 \(w\) 次这个前提好像根本没有考虑?但是 dp 写下来感觉又很顺,子状态的转移感觉也没什么问题,希望未来能有某位大佬看到然后帮忙解答疑惑。