题目链接
题意
猜数游戏,每次返回的答案会异或上一个伪随机数生成器。
1 2 3 4 5 6 7 8 9
| const long long p=998244353; const int n; long long seed; int gen() { seed=seed*n%p; return seed%n; }
|
思路
考虑先用一定次数的 query(0)^2
计算出 seed,再用常规的二分思路解决猜数游戏。
发现 seed 如果没有对 p 取模的话,gen()
的返回值一定是 0。
取模相当于减去若干个 p,设第一次 gen()
的返回值为 −v1,则 v1≡p⋅⌊pseed⋅n⌋(modn)。
由于 p⊥n,所以 v1 与 ⌊pseed⋅n⌋ 在模 n 意义下一一对应,记 ⌊pseed⋅n⌋=w1。
考虑第二次 gen()
的返回值为 −v2,类似地推出式子 v2≡p⋅⌊pseed⋅n2−w1p⋅n⌋(modn)。
归纳总结得到 vi≡p⋅⌊pseed⋅ni−∑j=1i−1wjp⋅ni−j⌋(modn),最后我们可以得到所有的 wi。
当 i 足够大时(设为 l),式子 ⌊pseed⋅nl−∑i=1l−1wip⋅nl−i⌋modn 会对每一个 seed 都有不同的取值,就可以通过 nl(∑i=1l−1wip⋅nl−i)−o(p)(o(p) 为小于 p 的一个「误差」)来计算 seed。
分析发现 l=lognp+1 即可消除误差的影响。
总询问次数为 lognp+1+log2x,其实卡得不是很紧。
实现
注意不要把 l 开得太大以免爆 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) { 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; } } } }
|