ACM - ICPC World Finals 2013 B Hey, Better Bettor

原题下载: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.由于函数定义域是整数集,所以要注意三分的结束条件

 

posted on 2014-01-27 11:51  SnowyJone  阅读(786)  评论(0编辑  收藏  举报