最近,scx 写了一个奇怪的程序,伪代码如下:
//input: integers x, k, p
a = x;
for(step = 1; step <= k; step = step + 1){
rnd = [random integer from 1 to 100];
if(rnd <= p)
a = a * 2;
else
a = a + 1;
}
s = 0;
while(remainder after dividing a by 2 equals 0){
a = a / 2;
s = s + 1;
}
现在 scx 想知道,给定 $x, k, p$,求变量 $s$ 的期望值。
输入共一行,包含三个正整数 $x, k, p$ ($1 \leq x \leq 10^9, 1 \leq k \leq 200, 0 \leq p \leq 100$)。
输出变量 $s$ 的期望值,绝对误差或相对误差不超过 $10^{-6}$。
记 $f_{i, j}$ 为原始的 $a$ (即 $x$) 经过 $i$ 次外层循环后,$a$ 再加上 $j$,所得的变量 $s$ (ctz
,后缀 0 计数) 的期望值,则有 $f_{0, j} = \mathrm{ctz}(x + j)$,其中 ctz(x)
为后缀 0 计数函数,原型为 __builtin_ctz(x)
。
再记 $p$ 为 $\times 2$ 的概率 (= (double)p * 0.01)
),$q = 1 - p$ 位 $+ 1$ 的概率。
考虑转移,若 $j$ 为奇数,则第 $i$ 次的结果为 $\times 2$ 就无意义了,所以只能由第 $i$ 次的结果为 $+ 1$ 转移过来,即
$$ f_{i, j} = f_{i-1, j+1} \cdot q $$
若 $j$ 为偶数,则第 $i$ 次不仅可以 $+ 1$,还可以是 $\times 2$。由于是乘 $2$ 后再加上 $j$,相当于加上 $\dfrac j2$ 后再乘 $2$,因此 $j$ 为偶数的转移是
$$ f_{i, j} = f_{i-1, j+1} \cdot q + \left( f_{i-1, j/2} + 1 \right) \cdot p $$
总转移为:
$$ f_{i, j} = f_{i-1, j+1} \cdot q + [2 \mid j] \left( f_{i-1, j/2} + 1 \right) \cdot p $$
由于真正有影响的不超过 $256$,所以第二维可以只算到 $O(k)$。
答案当然就是 $f_{k, 0}$,时间复杂度 $O(k^2)$,轻松水过。
#include <bits/stdc++.h>
#define K 544
using namespace std;
int x, k, i, j;
double p, q, f[K][K];
int main(){
scanf("%d%d%d", &x, &k, &i);
p = (double)i * 0.01; q = 1.0 - p;
memset(f, 0, sizeof f);
for(j = 0; j <= k; ++j) f[0][j] = (double)__builtin_ctz(x + j);
for(i = 1; i <= k; ++i)
for(j = 0; j <= k; ++j){
f[i][j] += f[i - 1][j + 1] * q;
f[i][j << 1] += (f[i - 1][j] + 1.0) * p;
}
printf("%.15lf\n", f[k][0]);
return 0;
}
坑1:初始化的时候,不要忘记 $0$,就是说对 $0 \leq j \leq k$,有 $f_{0, j} = \mathrm{ctz}(x + j)$。
坑2:然后就是 $+ 1$ 和 $- 1$ 搞清楚,还有 $\times 2$ 和 $\div 2$,由于后者只在 $j$ 为偶数时转移,因此可以直接从 $f_{i-1, j}$ 转移到 $f_{i, 2j}$。