题意:需要摆放 n n n 个骨牌,但每次放骨牌有 p l p_l pl 的概率向左倒,有 p r p_r pr 的概率向右倒(也就是有 1 − p l − p r 1-p_l-p_r 1−pl−pr 的概率不会倒),每次倒下的骨牌会将左边(右边)的所有骨牌都推到,问按照最优策略摆放 n n n 个骨牌的摆放次数期望。有最多100个测试点, 1 ≤ n ≤ 1000 1\le n \le 1000 1≤n≤1000 。
先考虑已经放好了
n
−
1
n-1
n−1 块骨牌,放第
n
n
n 块骨牌时的最优策略。显然此时总共有
n
n
n 种摆放方法,分别对应放在
n
−
1
n-1
n−1 块骨牌的
n
n
n 个间隔处,假设放的位置前面有
i
i
i 块,则后面有
n
−
1
−
i
n-1-i
n−1−i 块,那么最优策略就是选取:
E
n
=
min
i
{
1
−
p
r
1
−
p
l
−
p
r
E
i
+
1
−
p
l
1
−
p
l
−
p
r
E
n
−
1
−
i
+
1
1
−
p
l
−
p
r
}
(
0
≤
i
≤
n
−
1
)
E_n=\min\limits_{i}\{\frac{1-p_r}{1-p_l-p_r}E_i+\frac{1-p_l}{1-p_l-p_r}E_{n-1-i}+\frac{1}{1-p_l-p_r}\}\qquad (0\le i\le n-1)
En=imin{1−pl−pr1−prEi+1−pl−pr1−plEn−1−i+1−pl−pr1}(0≤i≤n−1)
详细解释上面的式子:在中间放一个骨牌会有三种情况,放稳了、往左倒、往右倒。三种情况的概率题目已经给出。把第
n
n
n 块放好需要的次数期望为
1
1
−
p
l
−
p
r
\frac{1}{1-p_l-p_r}
1−pl−pr1 (用的是多次独立的伯努利实验中,第一次成功的期望试验次数为
1
p
\frac{1}{p}
p1 ,这里的
p
p
p 就是
1
−
p
l
−
p
r
1-p_l-p_r
1−pl−pr);进而没放稳的次数期望为前面的
1
1
−
p
l
−
p
r
−
1
=
p
l
+
p
r
1
−
p
l
−
p
r
\frac{1}{1-p_l-p_r}-1=\frac{p_l+p_r}{1-p_l-p_r}
1−pl−pr1−1=1−pl−prpl+pr 次,这么多次中向左倒的次数期望为
p
l
+
p
r
1
−
p
l
−
p
r
⋅
p
l
p
l
+
p
r
=
p
l
1
−
p
l
−
p
r
\frac{p_l+p_r}{1-p_l-p_r}\cdot \frac{p_l}{p_l+p_r}=\frac{p_l}{1-p_l-p_r}
1−pl−prpl+pr⋅pl+prpl=1−pl−prpl ,向右倒的次数期望为
p
l
+
p
r
1
−
p
l
−
p
r
⋅
p
r
p
l
+
p
r
=
p
r
1
−
p
l
−
p
r
\frac{p_l+p_r}{1-p_l-p_r}\cdot \frac{p_r}{p_l+p_r}=\frac{p_r}{1-p_l-p_r}
1−pl−prpl+pr⋅pl+prpr=1−pl−prpr 。那么对应着此时需要放的总次数为:在这一位不断重新尝试放的次数+往右倒后又把右边全部摆好的次数+往左倒后又把左边全部摆好的次数,为
1
1
−
p
l
−
p
r
+
p
l
1
−
p
l
−
p
r
E
i
+
p
r
1
−
p
l
−
p
r
E
n
−
1
−
i
\frac{1}{1-p_l-p_r}+\frac{p_l}{1-p_l-p_r}E_i+\frac{p_r}{1-p_l-p_r}E_{n-1-i}
1−pl−pr1+1−pl−prplEi+1−pl−prprEn−1−i 。最后再加上前面
n
−
1
n-1
n−1 个摆好的期望次数
E
i
+
E
n
−
1
−
i
E_i+E_{n-1-i}
Ei+En−1−i 整理一下可得上面的式子。
那么基于这个式子,已经可以使用 O ( n 2 ) O(n^2) O(n2) 的动态规划通过本题,对于每个 i i i 枚举中间的转移点 k k k 即可。
但是本题还可以继续优化。首先我们知道 E i E_i Ei 一定是单调递增的(考虑其表示的含义),由于 p l p_l pl 和 p r p_r pr 是提前给定的,可以将转移方程简写为 E n = min i { a E i + b E n − 1 − i + c } E_n=\min\limits_{i}\{aE_i+bE_{n-1-i}+c\} En=imin{aEi+bEn−1−i+c} ,在 i i i 逐渐递增的过程中, E i E_i Ei 不断增大, E n − 1 − i E_{n-1-i} En−1−i 不断减小,这时如果 E i E_i Ei 是导数不断增大的凸函数的话, a E i + b E n − 1 − i + c aE_i+bE_{n-1-i}+c aEi+bEn−1−i+c 就会呈现出开口向上的单峰( i i i 大时增量大,则开始 E n − 1 − i E_{n-1-i} En−1−i减小的快总体减小,后面 E i E_i Ei增长快总体增大),这样的话可以用三分法取极值,复杂度降到 O ( n log n ) O(n\log n) O(nlogn) 。并且还能推导出决策点 k k k 单调递增的结论(这个放到后面推导)。
那么首要问题是如何证明 E i E_i Ei 是导数不断增大的凸函数(或者可能是是凹函数?)
考虑用数学归纳法。假设 p l ≤ p r p_l\le p_r pl≤pr 即 1 ≤ a ≤ b ≤ c 1\le a\le b \le c 1≤a≤b≤c 先枚举前几项: E 0 = 0 , E 1 = c , E 2 = min ( b c + c , a c + c ) = a c + c , E 3 = min ( b ( a c + c ) + c , a ( a c + c ) + c , a c + b c + c ) = min ( a 2 , b ) ⋅ c + a c + c E_0=0,\ E_1=c,\ E_2=\min(bc+c,ac+c)=ac+c,\ E_3=\min(b(ac+c)+c,a(ac+c)+c,ac+bc+c)=\min(a^2,b)\cdot c+ac+c E0=0, E1=c, E2=min(bc+c,ac+c)=ac+c, E3=min(b(ac+c)+c,a(ac+c)+c,ac+bc+c)=min(a2,b)⋅c+ac+c 。可以算出增量满足 Δ 1 = c , Δ 2 = a c , Δ 3 = min ( a 2 , b ) ⋅ c \Delta_1=c,\ \Delta_2=ac,\ \Delta_3=\min(a^2,b)\cdot c Δ1=c, Δ2=ac, Δ3=min(a2,b)⋅c ,容易看出增量是不断变大的,所以 E i E_i Ei 很有可能为凸函数。
注意推导过程中如果取等号比如 a = 1 a=1 a=1 可能会导致函数丧失某些性质,但实际上是变成了更简单的情况,并且最后仍然符合我们推导出来的最终结论。
现在假设 E 0 ∼ E n − 1 E_0 \sim E_{n-1} E0∼En−1 均是凸函数(由于是离散的数据,满足凸函数性质只用满足 E i + 1 − E i E_{i+1}-E_i Ei+1−Ei是递增的即可,也就是折线的斜率不断增大)。那么 E n = min i { a E i + b E n − 1 − i + c } E_n=\min\limits_{i}\{aE_i+bE_{n-1-i}+c\} En=imin{aEi+bEn−1−i+c} ,设 f ( i ) = a E i + b E n − 1 − i + c f(i)=aE_i+bE_{n-1-i}+c f(i)=aEi+bEn−1−i+c ,则 f ′ ( i ) = a E i ′ − b E n − i − 1 ′ f'(i)=aE'_{i}-bE'_{n-i-1} f′(i)=aEi′−bEn−i−1′ , f ′ ′ ( i ) = a E i ′ ′ + b E n − i − 1 ′ ′ f''(i)=aE''_{i}+bE''_{n-i-1} f′′(i)=aEi′′+bEn−i−1′′ ,由于凸函数满足 E ′ ′ ≥ 0 E''\ge 0 E′′≥0 ,推出 f ′ ′ ( i ) ≥ 0 f''(i) \ge 0 f′′(i)≥0 ,则 f ′ ( i ) f'(i) f′(i) 单调递增,那么 f ( i ) f(i) f(i) 的最小值要不出现在端点处( f ′ ( i ) f'(i) f′(i)和 x x x轴没交点),要不就是出现在 f ′ ( i ) f'(i) f′(i) 的零点处,但三种情况都可以看成开口向上的单峰,而最优决策点就是最低点设为 k k k , k k k 两端的 f ( ) f() f() 均是往外增加的。
现在我们已经找到了最优决策点并且确定了决策点满足的一些性质了,但还是没证明在 n n n 处 E n E_n En 仍是凸函数,其实只要证明 E n − E n − 1 ≥ E n − 1 − E n − 2 E_n-E_{n-1} \ge E_{n-1}-E_{n-2} En−En−1≥En−1−En−2 即可,那么我们研究增量 E n − E n − 1 E_n-E_{n-1} En−En−1 有什么性质:
E n E_n En 的决策点已经确定(设为 k 1 k_1 k1),想要研究增量 E n − E n − 1 E_n-E_{n-1} En−En−1 就必须确定出 E n − 1 E_{n-1} En−1 的决策点(设为 k 2 k_2 k2),才能展开为统一形式比较或计算。用同样的方式分析,我们可以发现: E n − 1 E_{n-1} En−1 对应的 f n − 1 ′ ( i ) = a E i ′ − b E n − i − 2 ′ f'_{n-1}(i)=aE'_{i}-bE'_{n-i-2} fn−1′(i)=aEi′−bEn−i−2′ ,决策点 k 2 k_2 k2 同样取在 f ′ ( i ) f'(i) f′(i) 的零点处或者端点处,而这时我们观察到 f n − 1 ′ ( i ) ≥ f n ′ ( i ) f'_{n-1}(i)\ge f'_{n}(i) fn−1′(i)≥fn′(i) 恒成立(因为 E n − i − 2 ′ ≤ E n − i − 1 ′ E'_{n-i-2}\le E'_{n-i-1} En−i−2′≤En−i−1′),所以 f n − 1 ′ f'_{n-1} fn−1′ 永远在 f n ′ f'_{n} fn′ 的上方,基于这个性质我们可以推出,不管两者以什么方式分布(均有零点、均在 x x x轴单侧、只有一个条有零点),均会使 k 2 ≤ k 1 k_2\le k_1 k2≤k1 (只要画一下图列举一下各种情况很容易证明),也就是最重要的一条结论:决策点单调递增。
但其实,现在我们还没有推出来在
n
n
n 处
E
n
E_n
En 是凸函数,上面的结论仅是在
E
0
∼
E
n
−
1
E_0 \sim E_{n-1}
E0∼En−1 均是凸函数这个假设下成立的,不过,有了前面的推导,我们可以拼上最后一块拼图了:
{
E
n
=
a
E
k
1
+
b
E
n
−
1
−
k
1
+
c
E
n
−
1
=
a
E
k
2
+
b
E
n
−
2
−
k
2
+
c
k
3
≤
k
2
≤
k
1
E
n
−
2
=
a
E
k
3
+
b
E
n
−
3
−
k
3
+
c
\begin{cases} \ E_n\ \ =aE_{k_1}+bE_{n-1-k_1}+c \\ E_{n-1}=aE_{k_2}+bE_{n-2-k_2}+c &k_3\le k_2\le k_1 \\ E_{n-2}=aE_{k_3}+bE_{n-3-k_3}+c \\ \end{cases}
⎩
⎨
⎧ En =aEk1+bEn−1−k1+cEn−1=aEk2+bEn−2−k2+cEn−2=aEk3+bEn−3−k3+ck3≤k2≤k1此时设
a
(
E
k
2
−
E
k
3
)
=
x
2
,
b
(
E
n
−
3
−
k
3
−
E
n
−
2
−
k
2
)
=
y
2
a(E_{k_2}-E_{k_3})=x_2,\ b(E_{n-3-k_3}-E_{n-2-k_2})=y_2
a(Ek2−Ek3)=x2, b(En−3−k3−En−2−k2)=y2 ,由
E
i
E_i
Ei 的单调性有
x
2
>
y
2
x_2> y_2
x2>y2,同理设
a
(
E
k
1
−
E
k
2
)
=
x
1
,
b
(
E
n
−
2
−
k
2
−
E
n
−
1
−
k
1
)
=
y
1
a(E_{k_1}-E_{k_2})=x_1,\ b(E_{n-2-k_2}-E_{n-1-k_1})=y_1
a(Ek1−Ek2)=x1, b(En−2−k2−En−1−k1)=y1 ,有
x
1
>
y
1
x_1> y_1
x1>y1 ,再通过
E
0
∼
E
n
−
1
E_0 \sim E_{n-1}
E0∼En−1 是凸函数有
x
1
>
x
2
>
y
2
>
y
1
x_1>x_2>y_2>y_1
x1>x2>y2>y1 ,则
E
n
−
E
n
−
1
≥
E
n
−
1
−
E
n
−
2
E_n-E_{n-1} \ge E_{n-1}-E_{n-2}
En−En−1≥En−1−En−2 等价于
x
1
−
y
1
≥
x
2
−
y
2
x_1-y_1\ge x_2-y_2
x1−y1≥x2−y2 显然成立,
E
n
E_n
En 处是凸函数得证。
证明完成后就可以使用中间发现的结论优化算法了,一是三分法取极值,二是利用决策点单调递增,可以实现均摊复杂度为 O ( n ) O(n) O(n) 的算法。
#include <iostream>
#include <cstdio>
using namespace std;
const int N = 1e3 + 5;
int n;
double pl,pr,E[N];
double eps = 1e-7;
int main(){
while(scanf("%d",&n)==1,n){
scanf("%lf%lf",&pl,&pr);
double a = (1.0 - pr) / (1.0 - pl - pr);
double b = (1.0 - pl) / (1.0 - pl - pr);
double c = 1.0 / (1.0 - pl - pr);
E[0] = 0, E[1] = c;
int k = 0;
for(int i = 2; i <= n; i++){
while(k<i-1 && (a*E[k]+b*E[i-1-k]+c) - (a*E[k+1]+b*E[i-2-k]+c) >= -eps){
k++;
}
E[i] = a * E[k] + b * E[i-1-k] + c;
}
printf("%.2lf\n",E[n]);
}
return 0;
}