BSGS

用于解决$y^x \equiv z\ (mod\ p)$,给定$y,z,p \geq 1$,求解$x$。

当$gcd(y,p)=1$,在这种情况下,有可能给定$p$为质数。设$x=a * m - b$ ,$m= \lceil \sqrt p \rceil$,$ a \in [0,m+1)$,$b \in [0,m)$,那么原式变成$y^{a * m}=z * y^{b}$,先枚举$b$,计算$z * y^{b}(mod\ p)$,存到$map$中,再枚举$a$就好了。

SDOI2011

题意:给定$y,z,p$,计算1. $y^z (mod \ p)$;2. 满足$x * y \equiv z(mod \ p)$的最小非负整数的$x$;3.满足$y^x \equiv z\ (mod\ p)$的最小非负整数的$x$。

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
56
57
58
59
60
61
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll y,z,p;
ll qpow(ll x,ll n,ll mod){
ll res=1;
while(n){
if(n&1) res=res*x%mod;
x=x*x%mod;
n>>=1;
}
return res;
}
ll inv(ll x,ll mod){
return qpow(x,mod-2,mod);
}
void nnot(){
puts("Orz, I cannot find x!");
}
void BSGS(ll x,ll y){
map<ll,ll>pw;
if(y == 1){
puts("0");
return;
}
ll ans = -1, m = sqrt(p) + 1, xx, s = y;
for(ll i = 0; i < m; i++){
pw[s] = i;
s = s * x % p;
}
xx = qpow(x, m, p), s = 1;
for(ll i = 1; i <= m + 1; ++i){
s = s * xx % p;
if(pw.count(s)){
ans = i * m - pw[s];
break;
}
}
if(ans < 0) nnot();
else printf("%lld\n", ans);
}
int main(){
ll t,op;
scanf("%lld %lld",&t,&op);
while(t--){
scanf("%lld %lld %lld",&y,&z,&p);
if(op == 1) printf("%lld\n",qpow(y,z,p));
else if(op == 2){
ll tm=y%p;
if(tm==0 && z%p!=0){
nnot();
continue;
}
printf("%lld\n",z*inv(y,p)%p);
}
else{
if(y%p) BSGS(y,z);
else nnot();
}
}
}

当$gcd(y,p) \neq 1$时,就要用到$exgcd$理论。

将原式写成$y * y^{x-1}+k * p=z,k \in Z$,根据$exgcd$,当$d=gcd(y,p)$不是$z$的约数就不会有解,有:$$\frac{y}{d} * y^{x-1}+k * \frac{p}{d}=\frac{z}{d}$$

一直递归直到$d=1$,设之间的所有的$d$的乘积为$g$,递归$c$次,令$x’=x-c\ ,\ p’=\frac{p}{g}\ ,\ z=\frac{z}{g}$,$y^{x’} * \frac{y^c}{g} \equiv z’(mod \ p’)$

然后再用BSGS求解。

SPOJMOD Power Modulo Inverted

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
#include<bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;
typedef long long ll;
ll y,z,p;
ll qpow(ll x,ll n,ll mod){
ll res=1;
while(n){
if(n&1) res=res*x%mod;
x=x*x%mod;
n>>=1;
}
return res;
}
ll gcd(ll x,ll y){
return (y==0)?x:gcd(y,x%y);
}
ll exBSGS(){
if(z==1) return 0;
map<ll,ll>pw;
ll cnt=0,mul=1;
for(ll d=gcd(y,p); d!=1; d=gcd(y,p)){
if(z%d) return -1;
++cnt,z/=d,p/=d,mul=(mul*y/d%p);
if(z == mul) return cnt;
}
ll s=z,m=sqrt(p)+1;
for(ll i=0; i<m; i++){
pw[s]=i;
s=s*y%p;
}
ll x=qpow(y,m,p);
s=mul;
for(ll i=1; i<=m; i++){
s=s*x%p;
if(pw.count(s)) return i*m-pw[s]+cnt;
}
return -1;
}
int main(){
while(scanf("%lld %lld %lld",&y,&p,&z)&&(y||z||p)){
y%=p;
z%=p;
ll ans=exBSGS();
if(ans < 0) puts("No Solution");
else printf("%lld\n",ans);
}
}

generator 2

2019年牛客多校第五场。

题意:你有一个$n$个数字的递推式:$x_{i} \equiv (a * x_{i-1} + b)mod \ p$ ,给定$n$,$x_0$,$a$,$b$,$p$,其中$p$是素数,询问$Q$次,求一个最小的下标$i$,使得$x_i \equiv v (mod \ p)$。

题解:很容易得到关于$x_i$的通项公式:$$x_i = a^i * x_0 + b * \sum_{j=0}^{i-1} a^j = a^i * x_0 + b * (\frac{a^i - 1}{a - 1}) = a ^ i * (\frac{b}{a-1}+x_0) - \frac{b}{a-1}$$

那么,可以化成$$a^i \equiv \frac{(a-1) * v+b}{(a-1) * x_0+b} (mod \ p)$$

由于题目需要查询的次数有$1000$次,直接套上面的$BSGS$复杂度太大,所以就需要预处理,我们看上面的代码,每次进行算法时其实都有一次for循环是预处理,我们把这一步放在查询前,改用unorder_map或者手写hash存值。

进一步优化,我们把预处理的时间增加,而减少查询时间,将预处理改成$\left \lceil p^{\frac{2}{3}} \right \rceil$,然后将查询的时间改成$\left \lceil p^{\frac{1}{3}} \right \rceil$。

注意这题$a$等于1或者0需要特判。

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

struct Hash{
ll mod = 2908361;
ll cnt = 0, Head[2908361], nxt[1000006];
ll val[1000006], pos[1000006];
void init(){
memset(Head, -1, sizeof(Head)); cnt = 0;
}
void Insert(ll x, ll v){
ll key = x % mod;
pos[cnt] = x, val[cnt] = v, nxt[cnt] = Head[key];
Head[key] = cnt++;
}
ll Find(ll x){
ll key = x % mod;
for(ll i = Head[key]; ~i; i = nxt[i]){
if(pos[i] == x) return val[i];
}
return -1ll;
}
}hs;
ll qpow(ll x,ll n,ll mod){
ll res=1;
while(n){
if(n&1) res=res*x%mod;
x=x*x%mod;
n>>=1;
}
return res;
}
ll inv(ll x,ll p){
return qpow(x,p-2,p);
}
void Init(ll y,ll p){
hs.init();
ll m=ceil(pow(p,2.0/3.0));
ll s=1ll;
for(ll i=0; i<=m; i++){
hs.Insert(s,i);
s=s*y%p;
}
}
ll BSGS(ll y,ll z,ll p){
if(z == 1) return 0;
ll ans=-1,m=ceil(pow(p,1.0/3.0)),s=inv(z,p);
ll up=ceil(pow(p,2.0/3.0));
ll x=qpow(y,up,p);
for(ll i=1; i<=m; i++){
s=s*x%p;
ll fin=hs.Find(s);
if(fin!=-1){
ans=i*up-fin;
break;
}
}
return ans;
}
int main(){
ll n,p,x,a,b,v,T;
scanf("%lld",&T);
while(T--){
scanf("%lld %lld %lld %lld %lld",&n,&x,&a,&b,&p);
ll q;
scanf("%lld",&q);
Init(a,p);
while(q--){
scanf("%lld",&v);
if(a==0){
if(x==v) puts("0");
else if(b==v) puts("1");
else puts("-1");
continue;
}
if(a==1){
ll ans=((v-x)*inv(b,p)+p)%p;
if(ans>=n) ans=-1;
printf("%lld\n",ans);
continue;
}
ll z=(((a-1)*v+b)%p*(inv(((a-1)*x+b)%p,p)))%p;
ll ans=BSGS(a,z,p);
if(ans >= n) ans=-1;
printf("%lld\n",ans);
}
}
}
thanks!