原题下载:http://icpc.baylor.edu/download/worldfinals/problems/icpc2013.pdf
这题真心的麻烦……程序不长但是推导过程比较复杂,不太好想
题目翻译:
问题描述
“在赌场里,基本原则就是让他们玩下去以及让他们再来玩。他们玩得越久,他们会输的越多,最后,我们会得到一切”
(摘自1995年的电影Casino)
最近的经济衰退还没有影响到娱乐场所,包括赌场。赌场吸引广大玩家的竞争是很残酷的,有些赌场已经开始提供一些看上去很好的措施。有一个赌场正在提供以下的优惠:你可以在这个赌场里赌很多次。当你赌完之后,如果你的总资金减少了,这个赌场会把你损失的x%退还给你。显然,如果你赚了,你可以留下所有的钱。这个服务没有时间限制和金钱限制,但是你只能赎回一次。
为简单起见,假定所有的赌局会花费1块钱,如果你赢得了赌局,会返还2块钱。现在假设x等于20。如果你一共进行了10次赌局,且只赢得了其中3个,那么你一共会损失3.2块钱。如果你赢得了6次赌局,你会赢得2块钱。
给定x和赢得赌局的概率p%,写一个程序,计算在最优策略下你的最大期望收益。
输入格式
每个输入文件只包含一组测试数据。每个测试数据包含两个数字,第一个数字x(0≤x<100)表示返还比例,第二个数字p(0≤p<50)表示获胜概率。x和p最多只包含两位小数
输出格式
对每个测试数据输出一行,表示最大期望获利,当你的答案与标准输出的误差在0.001以内时,被认为是对的
样例输入
0 49.9
样例输出
0.0
样例输入
50 49.85
样例输出
7.10178453
数据规模和约定
0≤x<100,0≤p<50,x和p最多只包含两位小数
题目大意:
你去进行赌博,有p%的胜率,每赌一场花费1元,赢了会得到2元,输了就输了(^_^),你可以无限制赌下去,你可以选择在适当的时候使用一次返款机会,即如果你当前输着钱,赌场会返还给你你输钱的x%,但是只能返还一次,求最后可以赚到钱的期望值
思路分析:
这道题是典型的求数学期望的题,考虑到返款只能用一次,所以我们一定是在结束的时候使用这次机会。由于我们可以进行无限局赌博,所以我们的期望值与当前赌的局数无关,而是与当前赚了多少钱有关。我们定义\( f_i \)表示当前赚的钱数为\( i \)时所得收益的数学期望。此时我们就有了赌或不赌这两种决策方式,如果选择不赌且当前盈利为负(\( i < 0\))时,会得到\(|i|x\%\)的返款,我们可以得到关系式
\[f_i=max\left\{\begin{array}{ll}i&\mbox{i>0 且选择不赌}\\\left(1-x\%\right)i&\mbox{i<0 且选择不赌}\\p\%f_{i+1}+\left(1-p\%\right)f_{i-1}&\mbox{选择继续赌下去}\end{array}\right.\]
我们先在坐标系上把选择不赌的曲线画出来,会得到一条折点在原点的折线。假设现在坐标系中我们已经得到了当前的最优解,将它逐步调整成实际的最优解,回句话说,对于所有满足\(p\%f_{i+1}+\left(1-p\%\right)f_{i-1}>f_i\)的点全部用\(p\%f_{i+1}+\left(1-p\%\right)f_{i-1}\)来更新直至全部无法更新,由于\(p\%<50\%\),而且我们的折线的两支都是单增的,所以当i-1、i、i+1三个点位于一条直线上时我们始终不会更新i(以为不可能更优),所以我们只可能从原点开始进行更新,而且我们不可能无限制地更新(想想也知道,如果都输干净了还不如不再赌下去,赢得多了也应该见好就收),因为这个递推函数的增长率是渐进为线性的,也就是说会存在上下界两个点使得无法继续更新。至此我们已经找到了问题的突破口——不停地对当前序列进行更新直至无法更新,\(f_0\)即为所求
但是尽管如此,我们可能更新的上下界依然可能非常大,这样直接更新非常容易超时。换一种思路,如果我们已知上下界的话,我们可以通过求通项来计算出\(f_0\)的值
对于上下界内的这部分我们都满足\[p\%f_i=f_{i-1}+\left(1-p\%\right)f_{i-2},\]然后我们就得到了一个二阶线性递推数列,特征方程为\[x^2=\frac{1}{p\%}x+\frac{1-p\%}{p\%},\]因式分解之后解得\[x_1=1,\qquad x_2=\frac{1-p\%}{p\%},\]然后得到通项公式\[f_n=\left(\frac{1-p\%}{p\%}\right)^n\alpha+\beta,\]则\(f_0=\alpha+\beta\)即为所求,由于已知上下界i和j(i < j),上下界正巧和不赌的两种情况相等,因此我们可以得到两个方程\[\left\{\begin{array}{l}\left(1-x\%\right)i=\left(\frac{1-p\%}{p\%}\right)^i\alpha+\beta\\j=\left(\frac{1-p\%}{p\%}\right)^j\alpha+\beta\end{array}\right.,\]
为了简单起见,我们设\(k_w=\left(\frac{1-p\%}{p\%}\right)^w\),然后解得\[\alpha=\frac{\left(1-x\%\right)i-j}{k_i-k_j},\\\beta=j-k_j\alpha,\]
但是枚举上下界依旧会超时,我们通过对小数据暴搜发现当下界已定的时候\(f_0\)是关于上界的一个单峰的上凸函数,同样地下界已定时\(f_0\)是关于上界的单峰凸函数(这一点暂时不会证明……只是观察发现的,但事实上它真的成立,希望会证明的朋友不吝赐教),然后就可以用三分法求单峰函数极值(先三分下界,里面套着三分上界),最后可以得到答案。对三分法不熟悉的同学可以参照这个博客http://chenjianneng3.blog.163.com/blog/#m=0
算法流程:
三分下界 里面套着三分上界,计算出对应的\(\alpha+\beta\),(注意应用快速幂),求出极值
参考代码:
1 //date 20140126 2 #include <cstdio> 3 #include <cstring> 4 const int inf = 1000000; 5 const double EPS = 1e-9; 6 int l, r, m1, m2; 7 double x, p, q, ans; 8 void frenew(double &x, double y){if(y - x > EPS)x = y;} 9 inline double pow(double n, int x) 10 { 11 double ans = 1.0; 12 while(x) 13 { 14 if(x & 1)ans *= n; 15 n *= n; 16 x >>= 1; 17 } 18 return ans; 19 } 20 inline double calc(int i, int j) 21 { 22 double sig = i * (x - 1.0) + j; 23 int n = j - i; 24 double a1 = sig * (q - 1.0) / (pow(q, n) - 1.0); 25 if(a1 < EPS)return 0.0; 26 double sig2 = a1 * (pow(q, -i) - 1.0) / (q - 1.0); 27 double res = sig2 + i * (1 - x); 28 return res > EPS ? res : 0.0; 29 } 30 inline double work2(int w) 31 { 32 int l = 1, r = inf, v1, v2; 33 while(l + 2 < r) 34 { 35 v1 = (l + l + r) / 3; 36 v2 = (l + r + r) / 3; 37 if(calc(w, v1) - calc(w, v2) < -EPS)l = v1; else r = v2; 38 } 39 double res = calc(w, l); 40 frenew(res, calc(w, l + 1)); 41 frenew(res, calc(w, r)); 42 return res; 43 } 44 inline double solve() 45 { 46 int l = -inf, r = 0, v1, v2; 47 while(l + 2 < r) 48 { 49 v1 = (l + l + r) / 3; 50 v2 = (l + r + r) / 3; 51 if(work2(v1) - work2(v2) < EPS)l = v1; else r = v2; 52 } 53 ans = work2(l); 54 frenew(ans, work2(l + 1)); 55 frenew(ans, work2(r)); 56 return ans; 57 } 58 int main() 59 { 60 // freopen("bettor.in", "r", stdin); 61 // freopen("bettor.out", "w", stdout); 62 63 while(scanf("%lf%lf", &x, &p) != EOF) 64 { 65 x /= 100.0; p /= 100.0; 66 q = (1.0 - p) / p; 67 printf("%.9lf\n", solve()); 68 } 69 return 0; 70 }
需要注意的细节:
1.注意浮点运算的误差和比较
2.对无穷大值不可以直接求幂,,需要进行特判
3.由于函数定义域是整数集,所以要注意三分的结束条件