0%

「PR #3」猜数

题目链接

题意

猜数游戏,每次返回的答案会异或上一个伪随机数生成器。

1
2
3
4
5
6
7
8
9
const long long p=998244353; // p 是质数,p<=1e18
const int n; // n=3 或 n=4
long long seed; // 0<=seed<p
int gen()
{
seed=seed*n%p;
return seed%n;
}

思路

考虑先用一定次数的 query(0)^2 计算出 seed\operatorname{seed},再用常规的二分思路解决猜数游戏。

发现 seed\operatorname{seed} 如果没有对 pp 取模的话,gen() 的返回值一定是 00
取模相当于减去若干个 pp,设第一次 gen() 的返回值为 v1-v_1,则 v1pseednp(modn)v_1\equiv p\cdot\lfloor\frac{\operatorname{seed}\cdot n}p\rfloor\pmod n
由于 pnp\bot n,所以 v1v_1seednp\lfloor\frac{\operatorname{seed}\cdot n}p\rfloor 在模 nn 意义下一一对应,记 seednp=w1\lfloor\frac{\operatorname{seed}\cdot n}p\rfloor=w_1

考虑第二次 gen() 的返回值为 v2-v_2,类似地推出式子 v2pseedn2w1pnp(modn)v_2\equiv p\cdot\lfloor\frac{\operatorname{seed}\cdot n^2-w_1p\cdot n}p\rfloor\pmod n
归纳总结得到 vipseednij=1i1wjpnijp(modn)v_i\equiv p\cdot\lfloor\frac{\operatorname{seed}\cdot n^i-\sum_{j=1}^{i-1}w_jp\cdot n^{i-j}}p\rfloor\pmod n,最后我们可以得到所有的 wiw_i

ii 足够大时(设为 ll),式子 seednli=1l1wipnlipmodn\lfloor\frac{\operatorname{seed}\cdot n^l-\sum_{i=1}^{l-1}w_ip\cdot n^{l-i}}p\rfloor\bmod n 会对每一个 seed\operatorname{seed} 都有不同的取值,就可以通过 (i=1l1wipnli)o(p)nl\frac{\left(\sum_{i=1}^{l-1}w_ip\cdot n^{l-i}\right)-o(p)}{n^l}o(p)o(p) 为小于 pp 的一个「误差」)来计算 seed\operatorname{seed}

分析发现 l=lognp+1l=\log_np+1 即可消除误差的影响。

总询问次数为 lognp+1+log2x\log_np+1+\log_2x,其实卡得不是很紧。

实现

注意不要把 ll 开得太大以免爆 int128

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include"guess.h"
#include<iostream>
using namespace std;
const int N=4,L3=40,L4=32;
long long p;
int n;
long long seed;
void init(int subtaskID,int t)
{
// do nothing.
return;
}
long long calcSeed()
{
int L=n==3?L3:L4;
int id[N];
for(int i=0,s=0;i<n;++i,s=(s+p)%n) {
id[(n-s)%n]=i;
}
__int128_t w=0,d=1;
for(int l=0;l<L;++l) {
w=w*n+id[query(0)^2];
d*=n;
}
long long s=(w*p/d+(w%d!=0))%p;
for(int l=0;l<L;++l) {
s=s*n%p;
}
return s;
}
int gen()
{
seed=seed*n%p;
return seed%n;
}
long long solve(long long _p,int _n)
{
p=_p,n=_n,seed=calcSeed();
for(long long l=1,r=1e18;l<=r;) {
long long mid=(l+r)/2;
switch(query(mid)^gen()) {
case 0: {
r=mid-1;
break;
}
case 1: {
return mid;
}
case 2: {
l=mid+1;
break;
}
}
}
}