JAGSpring2015C Casino
设\(f_i\)表示有\(i\)元钱时的获胜概率,那么我们有:\(f_0=0,f_n=1,f_m=\max\limits_{x=1}^{\min(m,n-m)}pf_{m+x}+(1-p)f_{m-x}\)。
\(p=0\):获胜概率为\(0\),可行的决策集合为\([1,m]\)。
\(p=1\):获胜概率为\(1\),可行的决策集合为\([1,m]\)。
\(p=\frac12\):获胜概率为\(\frac mn\),可行的决策集合为\([1,\min(m,n-m)]\)。
\(p>\frac12\):
直觉告诉我们此时选择\(x=1\)是最优的。因为概率对我们是有利的,所以时间拖得越长越有利。
不难验证\(x=1\)也是唯一的最优策略。
那么此时的\(f\)就是一个经典的赌徒输光模型,令\(q=\frac{1-p}p\),那么\(f_m=\frac{1-q^m}{1-q^n}\)。
\(p<\frac12\):
直觉告诉我们此时\(x\)应该尽可能大,也就是说\(x=\min(m,n-m)\)。
事实上这确实是最优的策略,但是还存在其它的最优策略。
首先代入得到:
然后我们就可以利用这个式子暴力迭代计算\(f_m\),最多\(10^4\)层即可。
注意到答案实际上只与\(\frac mn\)有关,因此我们考虑用\(f(x)\)代替\(f(nx)\)来进行计算。
那么也就是:
考虑将\(x\)展开为二进制,若\(x=\sum\limits_{i\ge0}2^{-e_i}\),那么\(f(x)=\sum\limits_{i\ge0}q^ip^{e_i}\),其中\(q=\frac{1-p}p\)。
我们可以推出\(\forall x\ge y\ge 0,f(x+y)\ge f(x)+qf(y)\)。(证明比较复杂,此处省略)
那么我们现在需要做的就是找到取等号的充要条件,先将\(x,y\)写成\(\frac{m+d}n,\frac{m-d}n\)的形式。
将\(\frac mn\)二进制展开,考虑其中的每一位01
即\(\frac mn=a2^{e+2}+2^e+b(a\in\mathbb N,b\in[0,2^e))\),那么可行的\(d\)的取值就是\((2^e\pm b)n\)。
#include<set>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
const double eps=1e-10;
int n,m;double p;std::set<int>ans;
double cal(int n,int m,int d)
{
if(!n||d>10000) return 0;
if(n==m) return 1;
int q=std::min(n,m-n);
return p*cal(n+q,m,d+1)+(1-p)*cal(n-q,m,d+1);
}
void getans(int n,int m)
{
for(;n&&n<m;m/=2,n%=m)
{
ans.insert(std::min(n,m-n));
if(m&1) break;
}
}
void print(double ans,int c)
{
printf("%.10lf\n%d\n",ans,c);
if(c<=200) for(int i=1;i<=c;++i) printf("%d ",i);
else
{
for(int i=1;i<=100;++i) printf("%d ",i);
for(int i=c-99;i<=c;++i) printf("%d ",i);
}
exit(0);
}
int main()
{
scanf("%lf%d%d",&p,&n,&m),p/=100;
if(fabs(p)<eps||fabs(p-1)<eps) print(p,n);
if(fabs(p-0.5)<eps) print(1.0*n/m,std::min(n,m-n));
if(0.5-eps<p) print((1-pow((1-p)/p,n))/(1-pow((1-p)/p,m)),1);
printf("%.10lf\n",cal(n,m,0)),getans(n,m);
printf("%d\n",(int)ans.size());
for(int x:ans) printf("%d ",x);
}