【bzoj3122】[Sdoi2013]随机数生成器 BSGS思想的利用
题目描述
给出递推公式 $x_{i+1}=(ax_i+b)\mod p$ 中的 $p$、$a$、$b$、$x_1$ ,其中 $p$ 是质数。输入 $t$ ,求最小的 $n$ ,使得 $x_n=t$ 。若不存在则输出-1。
输入
输入含有多组数据,第一行一个正整数 T ,表示这个测试点内的数据组数。
接下来 T 行,每行有五个整数 p,a,b,X1,t ,表示一组数据。保证 X1 和 t 都是合法的页码。
注意: p 一定为质数
输出
共T行,每行一个整数表示他最早读到第t页是哪一天。如果他永远不会读到第t页,输出-1。
样例输入
3
7 1 1 3 3
7 2 2 2 0
7 2 2 2 1
样例输出
1
3
-1
题解
BSGS思想的利用
(以下为思考过程)
早就知道网上的大部分题解:把通项公式变形为$x_{i+1}+c=a(x_i+c)\mod p$,然后转化为BSGS的经典方程$a^x\mod p=c$来求解。
然而这样需要exgcd之类的,还需要乱七八糟的特判,代码少说也有80+行。
考虑:直接利用BSGS的meet-in-the-middle思想,而不是无脑套方程求解。
(以上为思考过程)
为了方便,设$f(x)=(ax+b)\mod p$,$f^d(x)$表示$f(x)$的$d$次复合,即$f(f(...(x)))$,其中$f$的个数为$d$。
我们要求的就是满足$f^n(x_1)=t$的最小的$n$。
首先根据抽屉(鸽笼)原理,如果有解则一定不超过$p$。
把答案$n$写成$km-l$的形式,其中$m=\lceil\sqrt n\rceil$。
那么我们要求的就是$f^{km-l}(x_1)=t$。
我们把这个式子左右同时复合$l$层,变为$f^{km}(x_1)=f^l(t)$。
由于一次函数复合以后还是一次函数,因此可以直接处理出$f^l(x)$和$f^{km}(x)$中一次项和常数项的系数。具体方法:$a(cx+d)+b=acx+ad+b$。
然后把所有的$f^{km}(x_1)$按照处理出的系数算出来,放到哈希表中(代码中使用了map),其中值相同的只取$k$较小的。
再枚举$l$,按照处理出的系数算出$f^l(t)$,看哈希表中是否有这个数即可,如果有则用$km-l$更新答案。
思考一下为什么一般情况下这样是对的:因为$p$是足够大的质数,因此所有数在模意义下都是存在的,并且存在唯一的逆。所以对于已知的$(cx+d)\mod p=e$,知道$e$可以直接推出$x$。
这个做法当$a=0$时不成立,因为不存在唯一的逆。因此需要特殊处理。
时间复杂度$O(\sqrt n\log n)$
代码只有40行左右^_^
#include <map> #include <cmath> #include <cstdio> using namespace std; typedef long long ll; map<ll , ll> mp; map<ll , ll>::iterator it; ll u[50010] , v[50010]; int main() { int T; scanf("%d" , &T); while(T -- ) { ll p , a , b , x , t , m , i , ans = 1ll << 62; scanf("%lld%lld%lld%lld%lld" , &p , &a , &b , &x , &t); if(!a) { if(t == x) puts("1"); else if(t == b) puts("2"); else puts("-1"); } else { mp.clear() , m = ceil(sqrt(p)); u[0] = 1 , v[0] = 0; for(i = 1 ; i <= m ; i ++ ) u[i] = u[i - 1] * a % p , v[i] = (a * v[i - 1] + b) % p; for(i = 1 ; i <= m ; i ++ ) { x = (x * u[m] + v[m]) % p; if((it = mp.find(x)) == mp.end()) mp[x] = i; } for(i = 1 ; i <= m ; i ++ ) if((it = mp.find((t * u[i] + v[i]) % p)) != mp.end()) ans = min(ans , it->second * m - i + 1); if(ans == 1ll << 62) puts("-1"); else printf("%lld\n" , ans); } } return 0; }