「题解」Codeforces 441E Valera and Number
do_while_true
·
·
题解
感觉是 dp 好题啊!
这里令 n 作为原题面中的 k.
方法一:我认为的通过常规思路想出来的做法。
正常思路是设 f_{i,x} 表示操作了 i 步得到 x 的概率。但是由于 x 是 10^9 级别,复杂度过于大。
如果 \times 2 将其看作从后面填一个 0,那么 +1 操作对一个数产生的影响至多是 +n.
所以如果我们对后 p 位作个截断,并保证 2^p>n,只记录后 p 个二进制位的值,那么 +1 操作至多会进位一次。
如果进位的话,还要求出进位之后得到了末尾有几个零,所以要记录 p 位以上连续了几个 1.
但是这样的 f 转移出去如果产生进位,后面有 j 个 0,我们不能将这个 j 也记录到 f 的状态里去。所以另设 dp g_{i,j} 表示经过了 i 次操作,末尾 j 个 0 的方案数,并且要求 j\geq p+1(是进位进出来的)。
这样就做完了,f_{i,j,k} 表示进行了 i 次操作,p+1 位及以上有连续的 j 个 1,对最低的 p 个二进制位作截断得到的数是 k,概率为多少,并且要求 j 和 k 不能同时为 0,j 和 k 同时为 0 的方案要在 g 中算。
时间复杂度 $\mathcal{O}(n^3)$.
---
**方法二**:orz 机房大佬的妙法。
这种 $\times 2$ 和 $+1$ 的操作想到的是看二进制位,但是不断 $+1$ 产生进位会破坏掉很多美好的性质。能不能不让 $+1$ 操作产生一些不好的影响?于是想到了从后往前**倒着操作**,例如在一个 $\times 2$ 操作之前的 $+1$,对于后面的影响就变成了 $+2$,进而不会影响后面的最低位的值。
设 $f_{i,j,k}$ 后面 $i$ 个操作,给 $x$ 的影响是 $+j$,一共有 $k$ 次 $\times 2$ (也就是通过 $\times 2$ 获得的末尾的 $0$ 有 $k$ 个)的概率。
$j$ 的大小因为 $\times 2$ 的存在依然可能会很大?由于 $\times 2$ 相当于对最后一位作了一个截断,使得最后一位不会被前面的操作所影响,所以可以设 $f_{i,j,k}$ 表示后面 $i$ 个操作截断掉后面 $k$ 个位给 $x$ 的影响是 $+j$,这样前面接了一个 $+1$ 则转移到 $j+1$;接了一个 $\times 2$,若 $j$ 是偶数则转移到 $j/2$,否则最后这一位 1 再也不会动了,将概率乘上其末尾零的个数贡献给答案。如果操作都处理完了,把最初的那个 $x$ 看做 $x$ 个 $+1$ 直接算贡献即可。
这样已经能做到 $\mathcal{O}(n^3)$ 了,能不能更再给力一点!不难发现这个 $k$ 仅是在统计答案的时候算一个贡献用,由于期望具有线性性,所以**费用提前计算**一下,在 $\times 2$ 的时候直接乘上概率贡献给答案即可。这样就不需要在 dp 中记录 $k$ 了。
时间复杂度 $\mathcal{O}(k^2)$.
另一个理解角度是将末尾的若干个操作看作 $j$ 个 $+1$ 后面跟着 $k$ 个 $\times 2$:
- 如果来了一个 $+1$ 则将 $j\gets j+1$;
- 如果来了一个 $\times 2$:
- 若 $j$ 为偶数,则可以视作 $j/2$ 个 $+1$,而后面 $\times 2$ 多了一个。
- 若 $j$ 为奇数,则这个最低的 $1$ 就确定不会改变了,概率乘上 $k$ 贡献给答案。
这个思考方向也很有趣,实际上这两个解释是一个等价的 dp,只不过后一个巧妙地利用了转化意义做到一个简易的理解方式。
---
**方法三**:洛谷题解中的做法,并不知道怎么想到的???
此处 $p$ 即为原题面中的 $p\%$.
设 $f_{i,j}$ 表示操作了 $i$ 次之后的 $x$,再加上 $j$ 之后末尾 $0$ 的个数的期望。
先不管 $j$ 的范围,直接把转移写出来:
考虑当且这个 $x$ 上一步是 $\times 2$ 还是 $+1$:
- 上一步是 $\times 2$,那么新的 $x$ 是偶数,如果 $j$ 是奇数的话末尾零个数就是 $0$ 了;如果 $j$ 是偶数,那么可以视作 $\frac{x}{2}+\frac{j}{2}$ 的期望再 $+1$,即为 $f_{i-1,\frac{j}{2}}+1$,再乘上其概率 $p$.
- 上一步是 $+1$,那么 $i$ 步后的 $x$ 再加上 $j$ 的期望,即为 $(i-1)$ 步后的 $x$ 再加上 $j+1$ 的期望(从 $j$那里拿过来一个 $1$ 放到 $x$ 上)即为 $f_{i-1,j+1}$,再乘上概率 $(1-p)$.
为了更进一步观察这个转移的性质,将其写成刷表的形式:
- $f_{i+1,j\times 2}\gets (f_{i,j}+1)\times p$;
- $f_{i+1,j-1}\gets f_{i,j}\times (1-p)$.
最终答案为 $f_{n,0}$.考虑 dp 中一条对答案产生贡献的路径的 $j$,一操作会将 $j$ 乘二,但二操作只会将 $j$ 减一。所以产生贡献的一条路径经过的 $j$ 一定不会超过 $n$,如果走到某个 $f_{i,j}$ 其 $j>n$,那么就算后面都走第二类转移,也不会到达最终状态 $f_{n,0}$.所以第二维仅记 $[0,n]$ 范围内的即可。
时间复杂度 $\mathcal{O}(n^2)$.
(希望大概是这么理解的吧?)
第一种做法是常规思路通过找到性质来优化 dp 的值域范围。
第二种做法是通过转化问题(正着操作转成倒着操作)来优化 dp,还有一步费用提前计算的思想。
套路,都是套路。
[方法一的代码](https://codeforces.com/contest/441/submission/176220245)
[方法二的代码](https://codeforces.com/contest/441/submission/176226295)
[方法三的代码](https://codeforces.com/contest/441/submission/176226626)