露露、花花和萱萱最近对计算机中的随机数产生了兴趣。为大家所熟知的是,由计算机生成的随机数序列并非真正的随机数,而是由一定法则生成的伪随机数。
某一天,露露了解了一种生成随机数的方法,称为 Mersenne twister。给定初始参数 $m \in \mathbb Z^+$,$x \in \mathbb Z \cap [0, 2^m)$ 和初值 $M_0 \in \mathbb Z \cap [0, 2^m)$,它通过下列递推式构造伪随机数列 ${M_n}$:
[ \begin{equation} M_n = \left { \begin{aligned} &2M_{n-1}& ~2M_{n-1} < 2^m \ (2M_{n-1} &-2^m) \oplus x & ~2M_{n-1} \geq 2^m \ \end{aligned} \right. \end{equation} ]
其中 $\oplus$ 是二进制异或运算(C/C++ 中的 ^ 运算)。而参数 $x$ 的选取若使得该数列在长度趋于无穷时,近似等概率地在 $\mathbb Z \cap (0, 2^m)$ 中取值,就称 $x$ 是好的。例如,在 $m > 1$ 时 $x=0$ 就显然不是好的。
在露露向伙伴介绍了 Mersenne twister 之后,花花想用一些经典的随机性测试来检验它的随机性强度。为此,花花使用计算机计算了一些 $M_k$。
但细心的萱萱注意到,花花在某次使用二进制输入 $k$ 时,在末尾多输入了 $l$ 个 $0$。她正想告诉花花这个疏忽,然而花花已经计算并记录了错误的 $M_k$ 值而没有记录 $k$ 的值。虽然这其实不是什么致命的问题,但是在萱萱告诉花花她的这个疏漏时,作为完美主义者的花花还是恳求萱萱帮她修正 $M_k$ 的值。萱萱便把这个任务交给了他的 AI ——你!
【输入格式】
输入文件 random.in 的第一行包含一个正整数 $m$,第二行为二进制表示的 $x$(共 $m$ 个数,从低位到高位排列),第三行为二进制表示的 $M_0$(排列方式同 $x$),第四行包含一个整数 $type$。
接下来分为两种可能的情况:
- $type = 0$(萱萱记下了花花的输入):则第五行包含一个整数,表示萱萱记下来的正确的 $k$ 值。
- $type = 1$(萱萱未能记下花花的输入):则第五行为 $l$,第六行输入花花计算出错误的二进制表示的 $M_k$。
【输出格式】
输出文件 random.out 仅一行,为一个 $m$ 位的 01 串,表示你求得的正确的 $M_k$(同样要求从低位到高位排列)
【数据范围】
对于 $type = 0$ 的部分,$m, k \leq 10^6$
对于 $type = 1$ 的部分,$m \leq 10^3, k \leq 10^{18}, l \leq 10$,$x$ 是“好的”
每个数据点时限 20s,并且开启编译器优化
首先对于 Mersenne twister 这个生成方法,由于是二进制的移位,异或,我们可以认为一个数是 $GF(2)$ 上的多项式(或者说系数是在 $\bmod 2$ 意义下的多项式)
这样对于一个数 $a$ 就对应一个多项式 $A(x)$,并且移位和异或这两个操作都可以对应到多项式的运算
- $2a \Leftrightarrow xA(x)$
- $a \oplus b \Leftrightarrow A(x) \pm B(x)$
再看题目中的部分,对于 $2M_{n-1}<2^m$ 的情况,对应的就是 $M_n(x) = xM_{n-1}(x)$
对于另外的一种情况,第一步也是乘 $x$,这时得到的多项式最高项次数是 $m$,我们可以考虑将其放在 $\bmod x^m$ 下,这样 $2M_{n-1} - 2^m \Leftrightarrow xM_{n-1}(x) \bmod x^m$,那剩下的异或就相当于是要减去一个 $x$ 对应的多项式 $X(x)$,这只要改成把多项式放在 $\bmod (x^m + X(x))$ 意义下就可以了,然后对于第一种情况,由于最高项次数小于 $m$,模不对其产生影响
因此整个操作就可以统一规约成 $M_n(x) = xM_{n-1}(x) \bmod (x^m + X(x))$
具体的解释?我们来看看样例
当 $m = 10, M_0 = (1100000111)_2, x = (0011100111)_2$ 时,可以得到对应的多项式
\[\begin{eqnarray*} M_0(x) &=& x^9 + x^8 + x^2 + x + 1 \\ X(x) &=& x^7 + x^6 + x^5 + x^2 + x + 1 \end{eqnarray*}\]
然后来模拟一下
\[\begin{eqnarray*} M_1(x) &=& xM_0(x) &\pmod {x^{10} + X(x)}\\ &=& x^{10} + x^9 + x^3 + x^2 + x &\pmod {x^{10} + X(x)}\\ &=& x^9 + x^7 + x^6 + x^5 + x^3 + 1 \end{eqnarray*}\]
-
好,现在来看 $type=0$ 的部分,就是给定 $M_0$ 求 $M_k$
这相当于求 $x^kM_0(x) \bmod (x^m + X(x))$,因此利用快速傅里叶变换可以在 $\mathcal O(m\log m \log k)$ 的复杂度内计算完(关于多项式除法和求模…… 我过一段时间再写好了)
-
然后是 $type = 1$ 的部分,已知 $l, X(x), M_0(x), M_{k2^l}(x)$,要求求出 $M_k(x)$
因为已经知道了 \(x^{k2^l}M_0(x) \equiv M_{k2^l}(x) \pmod {x^m + X(x)}\)
那么两边同时乘上 $M_0(x)$ 的逆元就可以得到
\[x^{k2^l} \equiv M_{k2^l}(x)M_0^{-1}(x) \pmod {x^m + X(x)}\]
然后由于 $x$ 是“好的”,也就是说前 $2^m - 1$ 个数字中间 $\mathbb Z \cap (0, 2^m)$ 都会出现过一次(否则最后不会近似等概率),这样就可以转换为 $ x \equiv x^{2^m} \pmod {x^m + X(x)} $,然后可以得到
\[\begin{eqnarray*} x^{k2^l2^{m-l}} &\equiv& \left(M_{k2^l}(x)M_0^{-1}(x)\right )^{2^{m-l}} &\pmod {x^m + X(x)} \\ x^k &\equiv& \left(M_{k2^l}(x)M_0^{-1}(x)\right )^{2^{m-l}} &\pmod {x^m + X(x)} \end{eqnarray*}\]
剩下最后只要在左右两边乘上 $M_0(x)$ 就可以得到答案,复杂度是 $\mathcal O(m^2\log m)$
至于如何求在 $\bmod (2^m + X(x))$ 下的逆元?这可以用扩展欧几里德算法(我还是过一段时间写)然后你需要不断地优化常数,另外为了不出现精度误差,你可以选一个质数做 FFT,但要求在 $GF(2)$ 下,因此每次计算好了乘法之后要转换到这里(也就是模 2,注意正负)否则会爆出去,还有要注意的就是在求逆元的时候不能两次连续的乘法而不进行转换,原因也是因为这个
然后下面是有点长的代码
本文遵守 CC BY-NC 4.0 许可协议。