1 条题解
-
0
自动搬运
来自洛谷,原作者为

Mivik
AFO搬运于
2025-08-24 22:26:50,当前版本为作者最后更新于2020-11-28 17:03:58,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
首先来拆解一下随机数生成器生成 个随机数的过程。
- 调用
init(uint32_t seed),index置为 。 - 第一次调用
gen()时由于index为 会先调用twist()函数,从而将index置为0。 - 总共 次调用
gen(),第 次调用会依次返回temper(mt[i - 1])。
然后我们考虑设计出
temper(uint32_t x)的反函数。我们观察这个函数:static inline uint32_t temper(uint32_t x) { x ^= (x >> U); x ^= (x << S) & B; x ^= (x << T) & C; x ^= (x >> L); return x; }那么我们只需要实现下面两种操作:
- 根据
(x ^ (x >> L))还原出x - 根据
(x ^ ((x << L) & V))还原出x
就可以反向进行
temper操作了。我们以第一种反向操作为例。我们注意到
(x ^ (x >> L))二进制下最高的L位和x是相同的,于是我们就可以立即得到x二进制下的最高L位。接下来,由于我们知道了x最高的p1位,我们可以通过异或还原出x最高的2L位,以此类推。下图简单解释了这一流程(L = 6):AAAAAABBBBBB--------------------| --> x AAAAAABBBBBB--------------|------ --> (x >> L)我们第一步能知道
A的部分,然后通过异或就可以得出B的部分,以此类推。下面是简要的代码:inline uint32_t inv_shift_right(uint32_t v, uint8_t bits) { if (bits == 32) return v; // cur 表示已经得出的部分,k 表示当前已经推导了前几位。 uint32_t mask = (-1U) << (32 - bits), cur = v & mask; for (uint8_t k = bits; k < 32; k += bits) cur |= (v ^ (cur >> bits)) & (mask >>= bits); return cur; }同样地,第二种反向操作也可以通过相似的方式实现:
inline uint32_t inv_shift_left(uint32_t v, uint8_t bits, uint32_t m) { if (bits == 32) return v; uint32_t mask = (1U << bits) - 1, cur = v & mask; for (uint8_t k = bits; k < 32; k += bits) cur |= (v ^ ((cur << bits) & m)) & (mask <<= bits); return cur; }于是我们就可以反向
temper操作来得到刚调用twist()函数后的mt数组中的 个数。我们观察这个twist函数:// 得到下一状态 inline void twist() { for (int i = 0; i < N; ++i) { uint32_t tmp = (mt[i] & (1U << 31)) | (mt[keep_in_range(i + 1)] & 0x7fffffffU); tmp = (tmp & 1)? ((tmp >> 1) ^ A): (tmp >> 1); mt[i] = mt[keep_in_range(i + M)] ^ tmp; } index = 0; }我们发现不行了:我们没法倒推出
tmp,因为我们并不知道tmp一开始到底是不是奇数,如果暴力枚举是 的。怎么办呢?我们考虑从其它地方切入,观察这个
init()函数:// 使用种子初始化 inline void init(uint32_t seed) { mt[0] = seed; for (int i = 1; i < N; ++i) // 注意这里的无符号整型乘法溢出为定义行为,即得到的结果与真实结果在模 2 ^ 32 意义下同余 mt[i] = F * (mt[i - 1] ^ (mt[i - 1] >> 30)) + i; index = N; }由于 是奇数,那么 在模 意义下有逆元。使用这个逆元,我们可以通过
mt[i]解出(mt[i - 1] ^ (mt[i - 1] >> 30)),从而用上面提到的反向操作得出mt[i - 1]的值。也就是说我们只要知道了mt[N - 1]就可以推出整个调用twist()之前的mt数组,我们只需要枚举一下mt[N - 1]的值并检查一下就好了。时间复杂度 。 - 调用
- 1
信息
- ID
- 6276
- 时间
- 1000ms
- 内存
- 128MiB
- 难度
- 5
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者