原根与整数模$n$乘法群

对于一个正整数$n$,$1\sim n$有$\varphi(n)$(这里$\varphi$指欧拉函数)个与它互质的整数,形成了集合$\mathbb{Z_n^{*}}$。

如$\mathbb{Z}_{12}^*={1,5,7,11}$。这就是一个群。群有一个运算叫做乘法,乘法是满足结合律的。

群有一个性质,其中两个数乘起来后的数仍然在群里面,如$5\times 7\bmod 12=11$。

以及群是有逆元的。

现在我们尝试来描述它的结构。

原根

原根是研究它的一个有效工具。我们考虑一个与$n$互质的整数$a$,若$a^k\bmod n$包含了$\mathbb{Z_n^*}$中的所有元素(换句话说,$a^0\sim a^{\varphi(n)-1}\bmod n$互不相同),我们称它是一个模$n$的原根。

可以证明,只有$2,4,p^k,2p^k$(这里$p$是奇素数)有原根,且若$n$有原根,那么$\mathbb{Z_n^*}$中恰好有$\varphi(\varphi(n))$个原根。

因此,原根个数非常多,寻找原根是容易的。我们只需要随机或者从小往大枚举$x$,判定$x$是否是原根。

比较快的判定是分解$\varphi(n)$,然后依次枚举$\varphi(n)$的所有不同素因子$p_k$,若$x^{\frac{\varphi(n)}{p_k}}\equiv 1\pmod n$,则$x$就是$n$的原根。

整数模$n$乘法群

现在,对于存在原根的整数$n$,我们就可以清晰描述$\mathbb{Z_n^*}$了:这是一个$\varphi(n)$阶循环群。

简单的说,就是取一个模$n$的原根$g$,对于$\mathbb{Z_n^*}$中的每个整数$x$,都存在唯一一个$0\sim \varphi(n)-1$间的整数$k$,使得$g^k\equiv x\pmod n$。

那么我们可以把$x$与$k$对应起来,即令$f(x)=k$。容易发现,这么对应后,原来的乘法变成了加法,求逆变为了取负,即 $$ f(x\cdot y \bmod n)=(f(x)+f(y))\bmod \varphi(n) $$

$$ f(x^{-1}\bmod n)=(-f(x))\bmod \varphi(n) $$

这样,对于存在原根的整数$n$,利用这个对应,我们在$\mathbb{Z_n^*}$中做的乘法运算可以完全变为简单的模$\varphi(n)$意义下的加法。对于模意义下的加法,我们已经非常详细地了解了它的性质。因此这是一个解决数论问题的利器。

一个简单的小应用是解释:为什么$n$存在原根的情况下,$1\sim n$中恰好有$\varphi(\varphi(n))$个原根

随便取一个原根$g$,那对于每一个与原根互质的整数$x$。

$x=g^{f(x)},f(g)=1$

$x^0,x^1,\dots,x^{\varphi(n)-1} \pmod n$互不相同

$0f(x),1f(x),\dots,(\varphi(n)-1)f(x) \pmod {\varphi(n)}$也都互不相同

$\gcd(f(x),\varphi(n))=1$

对于其他的整数$n$,整数模$n$乘法群的结构会更加复杂,但它仍能写成若干个循环群的直积。(在群论中是有限群的一般分解定理。)一个比较简单的理解是我们可以把每个$\mathbb{Z}_n^*$中的整数对应到一个整数向量上,每一维的值是它在那个循环群上的“坐标”。两个整数的乘法可以理解为每一维分别相加,求逆理解为每一维分别取负(当然都是在每一维不同的模数的模意义下)。

稍微举个例子,仍然是$\mathbb{Z}_{12}^*={1,5,7,11}$

$1=(0,0)$

$5=(0,1)$

$7=(1,0)$

$11=(1,1)$

$5*7=?$

$(0,1)+(1,0)=(1,1)$

$5*7=11\pmod {12}$

$7*11=?$

$(1,0)+(1,1)=(0,1)$

$7*11=5\pmod 5$

稍显考虑$n=2^k(k\ge 3)$。可以证明此时的$\mathbb{Z}_n^*$是一个$2^{k-2}$阶循环群与$2$阶群的直积,两个群的生成元(原根)分别是$5$和$-1$。

(在模$2^k(k\ge 3)$时,$ord(5)=2^{k-2}$具体证明可以查数论书下面再举一个例子。)

$\mathbb{Z}_{16}^* ={1,3,5,7,9,11,13,15}$

$5^0\equiv 1,5^1\equiv 5,5^2\equiv 9,5^3\equiv 13, 5^4\equiv 1\cdots \pmod {16}$

$-1\equiv 15,-5\equiv 11,-9\equiv 7,-13 \equiv 3$

然后就有

$1=(0,0)$

$5=(1,0)$

$9=(2,0)$

$13=(3,0)$

$-1=(0,1)$

$-5=(1,1)$

$-9=(2,1)$

$-13=(3,1)$

这样,我们就完全解决了$n=p^k$,$p$是素数的情况。对于更一般的整数$n$,我们发现若将$n$分解为$p_1^{e_1}\cdot p_2^{e^2}\cdot \dots p_m^{e_m}$,那么模$n$意义下的乘法在每个$p_i^{e_i}$意义下是独立的(可以利用$\text{CRT}$转化)。因此,我们容易将$\mathbb{Z}_n^* $写为 $\mathbb{Z}_{p_i^{e_i}}^*$的直积。

若$\gcd(a,n)=1$,由欧拉定理(可以认为是一般有限群的拉格朗日定理的推论),物品,我们有$a^{\varphi(n)}\equiv 1 \pmod n$。

那么对满足$\gcd(a,n)=1$的$a$,我们定义$ord(a)$为最小的正整数$k$使得$a^k\equiv 1 \pmod n$,显然有$ord(a)\mid \varphi(n)$。

求阶是比较简单的:我们将$\varphi(n)$分解为$p_1^{e_1}\cdot p_2^{e_2}\cdot \ldots p_k^{e_k}$,然后初始时置$ord(a)=\varphi(n)$,每次看当前$ord(a)$除去一个$\varphi(n)$的质因子后是否仍有$x^{ord(a)}\equiv 1\pmod n$,若成立将$ord(a)$除去该质因子。

BSGS

求方程$a^k=b$的最小非负整数解$k$,或判断无解。这里$a,b$在一个代数结构(不一定是数)中,且代数结构的大小$|S|$有限。(这个代数结构要满足结合律,是一个半群,不需要有逆)

考虑取$M=\lceil\sqrt{|S|} \rceil$,预处理出$a^0,a^1,\ldots,a^{M-1}$和$a^M,a^{2M},\ldots,a^{M^2}$。(这里其实和平常的BSGS不一样,因为这个原本是对半群做的,这个方法并不用求逆元)

我们将$a^0,a^M,a^{2M},\ldots,a^{M^2}$插入一个哈希表中,枚举$q(0\leq q\leq m-1)$,在哈希表中查询$b\cdot a^q$

,如果找到的话,设对应的元素是$a^{cM}$,则$k=cM-q$

是一个可能值:具体地,若$a$有逆元,$a^{cM-q}$一定为为$b$,否则还需要代入检验。

假设乘法复杂度为$O(1)$,则算法复杂度为$O(\sqrt{|S|})$。

离散对数

对质数$p$和某个$p$的原根$g$,对$p\not\mid x$定义$x$模$p$意义下关于$g$的离散对数$x$为最小的非负整数$k$使得$g^k\equiv x \pmod p$,显然在$[0,p-1)$间仅有唯一一个$k$满足$g^k\equiv x \pmod p$。

当$a,b$都是$\mathbb{F}_p$中元素,且$a$是模$p$意义下原根,$b\ne 0$时,这其实就是求解离散对数,直接套用上述算法即可。

由于$a$有逆元,因此可以省去检验过程。

更一般的情况是求解方程$a^k\equiv b \pmod n$的最小非负整数解,此时$a$未必有逆元,因此套用上述算法时需要检验求出$a^k$是否$\equiv b\mod n$

以下可能稍微详细一点….

求解方程$a^x=c\pmod p$

如果$p$是质数,就是经典的BGSG,即大步小步算法。

考虑费马小定理:若$p$为素数,$\gcd(a,p)=1$,则有$a^{p-1}\equiv 1\pmod p$

而$a^0\equiv 1\pmod p$

发现出现了一个循环节,在$x\in [0,p-2]$时就已经包含了所有结果。

直接暴力枚举是$O(p)$

考虑分块优化,将所有的$x$表示为$kT+b$,此时要求$a^{kT}\equiv ca^{-b}\pmod p$

考虑将$ca^{-b}$放入Hash表中。

然后预处理出$a^{kT}$然后在Hash表中查询即可。

取$T=\sqrt p$得到复杂度为$O(\sqrt p)$,如果使用map实现多一个$\log$。

 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
const int N=2e5+10;

const int hst_mod=173219;
struct hash_map_t
{
    int head[hst_mod+10],cnt;
    struct edge {int nxt,key,val;}e[N<<1];
    inline int ser(int key)
    {
        int u=key%hst_mod;
        for(int i=head[u];i;i=e[i].nxt) if(key==e[i].key) return e[i].val;
        return -1;
    } 
    inline int ins(int key,int val)
    {
        if(ser(key)!=-1) return -1;
        int u=key%hst_mod;e[++cnt]=(edge){head[u],key,val};head[u]=cnt;
        return val;
    }
    inline int modify(int key,int val)
    {
        if(ser(key)==-1) return ins(key,val);
        int u=key%hst_mod;
        for(int i=head[u];i;i=e[i].nxt) if(e[i].key==key) return e[i].val=val;
    }
    inline void clear() {R(i,1,cnt) head[e[i].key%hst_mod]=e[i].nxt=0;cnt=0;}
}mp;

//map<int,int>mp;

int p,x,y;
int sq;
int mu,ff;
signed main()
{
    p=read(),x=read(),y=read();for(;sq*sq<=p;sq++);
    mu=1;ff=fpow(x,p-2,p);
    R(i,0,sq-1) mp.ins(mu*y%p,i),mu=mu*ff%p;
    ff=fpow(mu,p-2,p);mu=1;
    R(i,0,sq-1)
    { 
        
        int pos=mp.ser(mu);
        if(pos!=-1) 
        {
            writeln(i*sq+pos);
            return 0;   
        }
        /*
        if(mp.count(mu)) 
        {
            writeln(i*sq+mp[mu]);
            return 0;
        }
        */
        mu=mu*ff%p;
    }
    puts("no solution");
}

$n$个$1$可以表示为$\frac{10^n-1}{9}$,所以原式可以表示为$\frac{10^n-1}{9}\equiv k\pmod m$

化简得$10^n\equiv 9k+1\pmod m$,由于$m$是质数,BGSG解出来即可。

咕咕咕

接下来考虑$a,p$不一定互质的情况

当$a\perp p$时,在模$p$意义下$a$存在逆元,因此可以使用BSGS算法求解。于是我们想办法让他们变得互质。

具体地,设$d_1=\gcd(a,p)$,如果$d_1\not\mid c$则原方程无解。($a$的幂膜$p$之后一定残余$d$的倍数。)

否则将方程同时除以$d_1$,得到 $$ \frac{a}{d_1}\cdot a^{x-1}\equiv \frac{c}{d_1} \pmod {\frac{p}{d_1}} $$ 此时有$\frac{a}{d_1}\perp \frac{p}{d_1}$,于是就可以求逆了,需要exgcd

如果$a$和$\frac{p}{d_1}$不互质就再除,设$d_2=\gcd(a,\frac{p}{d_1})$,同样,若$d_2\not\mid \frac{c}{d_1}$则方程无解。

否则将方程再同时除以$d_2$,得到 $$ \frac{a^2}{d_1d_2}\cdot a^{x-2}\equiv \frac{c}{d_1d_2} \pmod {\frac{p}{d_1d_2}} $$ 同理,这样不断判断下去,直到$a\perp \frac{p}{d_1d_2\cdots d_k}$或者说$d_{k+1}=1$。

令$D=\prod\limits_{i=1}^k d_i$,于是方程就变成了 $$ \frac{a^k}{D} \cdot a^{x-k}\equiv \frac{c}{D} \pmod{\frac{p}{D}} $$ 由于$a\perp \frac{p}{D}$,于是推出$\frac{a_k}{D}\perp \frac{p}{D}$。这样$\frac{a^k}{D}$就有逆元了。

将其丢到方程右边,就是一个BSGS问题了,求解$x-k$再加上$k$就是原方程的解了。

由于不排除解小于等于$k$的情况,所以在消因子之前做一下$O(k)$枚举,直接验证$a_i\equiv b\pmod p$

大概就是如果出现 $$ a^{x-1}\equiv \frac{\frac{c}{\gcd(a,p)}}{\frac{a}{\gcd(a,p)}}\equiv 1\pmod{\frac{p}{\gcd(a,p)}} $$ 的情况,也许要特判输出$k$。此时同余式左边是$a^{x-k}$,因为$a^{x-k}\equiv 1\pmod p$,所以直接输出$k$。

当然如果你的BSGS能直接判$x=0$的情况就不需要了。

在菜鸡代码中的体现就是$a=c$

 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
int gcd(int a,int b) {return !b?a:gcd(b,a%b);}
void exgcd(int a,int b,int &x,int &y) {if(!b){x=1,y=0;return;}exgcd(b,a%b,y,x);y-=x*(a/b);}
inline int get_inv(int a,int p){int x,y;exgcd(a,p,x,y);return (x+p)%p;}
const int hst_mod=173219;
const int N=5e5+10;
struct hash_map_t
{
    int head[hst_mod+10],cnt;
    struct edge {int nxt,key,val;}e[N<<1];
    inline int ser(int key)
    {
        int u=key%hst_mod;
        for(int i=head[u];i;i=e[i].nxt) if(key==e[i].key) return e[i].val;
        return -1;
    } 
    inline int ins(int key,int val)
    {
        if(ser(key)!=-1) return -1;
        int u=key%hst_mod;e[++cnt]=(edge){head[u],key,val};head[u]=cnt;
        return val;
    }
    inline int modify(int key,int val)
    {
        if(ser(key)==-1) return ins(key,val);
        int u=key%hst_mod;
        for(int i=head[u];i;i=e[i].nxt) if(e[i].key==key) return e[i].val=val;
    }
    inline void clear() {R(i,1,cnt) head[e[i].key%hst_mod]=e[i].nxt=0;cnt=0;}
}mp;
int BSGS(int a,int c,int p)
{
    mp.clear();
    int sq,mu=1,ff=get_inv(a,p);
    for(sq=1;sq*sq<=p;sq++);
    R(i,0,sq-1) mp.ins(mu*c%p,i),mu=mu*ff%p;
    ff=get_inv(mu,p);mu=1;
    R(i,0,sq-1)
    {
        int pos=mp.ser(mu);
        if(pos!=-1) return i*sq+pos;
        mu=mu*ff%p;
    }
    return -1;
}
int solve(int a,int c,int p)
{
    if(a==c) return 1;
    int d=gcd(a,p);
    if(d==1) return BSGS(a,c,p);
    if(c%d) return -1;
    p/=d;
    return solve(a%p,(c/d)*get_inv(a/d,p)%p,p)+1;
}
int check(int a,int c,int p)
{
    if(c==1) return 0;
    if(!a) return !c?1:-1;
    return solve(a,c,p);
}

signed main()
{
    int a,c,p;
    while(1) 
    {
        a=read(),p=read(),c=read();
        if(!p) return 0;
        a%=p,c%=p;
        int ret=check(a,c,p);
        printf(ret<0?"No Solution\n":"%lld\n",ret);
    }
}

Pohlig-Hellman 算法

然后讲一下一个可以在某些情况下求$p=10^{18}$时离散对数的东西。

若我们求解$a^k=b$时,$a,b$在一个乘法群中$ord(a)=n$且$n=p_1^{e_1}\cdot p_2^{e_2}\cdot\ldots\cdot p_k^{e_k}$,则我们可以在$O(e_1\sqrt{p_1}+e_2\sqrt{p_2}+\dots + e_k\sqrt{p_k})$的时间复杂度内求出最小的$k$(若存在显然在$[0,n)$间唯一)。一个应用是求离散对数时,若$p-1$可以分解成若干小质因子乘积,则可以快速计算。

算法关键是若$n=pq$($q$为质数),我们令

$k=qu+v(0\leq u<p,0\leq v<q)$,我们可以在$O(\sqrt{q})$的时间复杂度内求出$v$:由于$a^k=a^{qu+v}=b$,且$a$的幂形成阶数为$n=pq$的循环群,那么两边取$p$次幂就有$(a^p)^v=b^p$,而$a^p$的幂形成阶数为$q$的循环群,可以直接BSGS求解最小的$v$,不存在则无解,否则必有$0\leq v<q$,那么两边同时乘上$a^{-v}$,再取$q$次幂就可以变为一个新的方程$a'^{k'}=b'$,但此时$a'$的幂形成阶数为$p$的循环群,递归求解即可。

$a^{qu+v}=b$

$(a^{qu+v})^p=b^p$

$a^{pqu}\cdot (a^p)^v=b^p$

$(a^p)^v=b^p$

$a^{qu}=b\cdot a^{-v}$

$(a^q)^u=b\cdot a^{-v}$

  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
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
#define lll __int128

ll gcd(ll x,ll y)
{
     if(!x) return y;
    if(!y) return x;
    ll t=__builtin_ctzll(x|y);
    x>>=__builtin_ctzll(x);
    do
    {
        y>>=__builtin_ctzll(y);
        if(x>y) swap(x,y);
        y-=x;
    }while(y);
    return x<<t;
}

inline ll ftimes(ull a,ull b,ull p)
{
    a%=p,b%=p;
    ll c=a*b-(ull)((ld)a/p*b)*p;c%=p;
    return c<0?c+p:c;
}
ll quick_pow(ll a,ll b,ll p) {ll ans=1;while(b){if(b&1)ans=(lll)ans*a%p;a=(lll)a*a%p;b>>=1;}return ans;}
int exgcd(int a,int b,int &x,int &y) {if(!b){x=1,y=0;return a;}int ret=exgcd(b,a%b,y,x);y-=x*(a/b);return ret;}
inline int get_inv(int a,int p){if(!a)return 1;int x,y;exgcd(a,p,x,y);return (x+p)%p;}
namespace por
{
    ll pr[]={2,3,5,7,11,13,17,19,37};
    int Miller_Rabin(ll p)
    {
        if(p<3) return p==2;
        if(p==3) return 1;
        if(!(p&1)) return 0;
        ll a=p-1,b=0;
        while(!(a&1)) a>>=1,++b;
        for(int i=0,j;i<=8;i++)
        {
            if(p==pr[i]) return 1;
            if(!(p%pr[i])) return 0;
            ll v=quick_pow(pr[i],a,p);
            if(v==1||v==p-1) continue;
            for(j=1;j<b;j++) {v=(lll)v*v%p;if(v==p-1)break;}
            if(v!=(p-1)) return 0;
        }
        return 1;
    }
    inline ll calc(ll x,ll c,ll p) {return ((lll)x*x+c)%p;}
    inline ll Pollard_Rho1(ll p)
    {
        ll s=0,t=0;
        ll c=(ll)rand()%(p-1)+1;
        ll val=1;
        for(int lim=1;;lim<<=1,s=t,val=1)
        {
            R(cnt,1,lim)
            {
                t=calc(t,c,p);
                val=ftimes(val,abs(t-s),p);
                if(!(cnt%127)) {ll g=gcd(val,p);if(g>1)return g;}
            }
            ll g=gcd(val,p);if(g>1)return g; 
        }
    }
    inline ll Pollard_Rho2(ll p)
    {
        ll c=rand()%(p-3)+3;
        ll t=calc(0,c,p),r=calc(calc(0,c,p),c,p);
        while(t!=r)
        {
            ll g=gcd(abs(t-r),p);
            if(g>1) return g;
            t=calc(t,c,p);
            r=calc(calc(r,c,p),c,p);
        }
        return p;
    }
    int ans;
    void solve(int n)
    {
        if(n<=ans||n<2) return;
        if(Miller_Rabin(n)){ckmax(ans,n);return;}
        int p=n;
        while(p>=n)p=Pollard_Rho1(n);
        while((n%p)==0) n/=p;
        solve(n),solve(p);
    }
    int mian(int n)
    {         
        srand(time(0));
        ans=0;
        solve(n);
        return ans;
    }
}
namespace DLP
{
    const int N=1e6+10;
    //const int hst_mod=3173219;
    inline int qpow(int a,int b) {int ret=1;while(b){if(b&1)ret=ret*a;a=a*a;b>>=1;}return ret;}
    inline int qpow(int a,int b,int p) {a%=p;int ret=1;while(b){if(b&1)ret=(lll)ret*a%p;a=(lll)a*a%p;b>>=1;}return ret;}
    /*
    struct hash_map_t
    {
        int head[hst_mod+10],cnt;
        struct edge {int nxt,key,val;}e[N<<1];
        inline int ser(int key)
        {
            int u=key%hst_mod;
            for(int i=head[u];i;i=e[i].nxt) if(key==e[i].key) return e[i].val;
            return -1;
        } 
        inline int ins(int key,int val)
        {
            if(ser(key)!=-1) return -1;
            int u=key%hst_mod;e[++cnt]=(edge){head[u],key,val};head[u]=cnt;
            return val;
        }
        inline int modify(int key,int val)
        {
            if(ser(key)==-1) return ins(key,val);
            int u=key%hst_mod;
            for(int i=head[u];i;i=e[i].nxt) if(e[i].key==key) return e[i].val=val;
        }
        inline void clear() {R(i,1,cnt) head[e[i].key%hst_mod]=e[i].nxt=0;cnt=0;}
    }mp;
    */
    map<int,int>mp;
    int tot_pri,pri[N/10];
    bool isnpr[N];
    inline void euler_pri(int lim=1000000)
    {
        int k;
        R(i,2,lim) 
        {
            if(!isnpr[i]) pri[++tot_pri]=i;
            R(j,1,tot_pri) 
            {
                k=i*pri[j];
                if(k>lim) break;
                isnpr[k]=1;
                if(i%pri[j]==0) break;
            }
        }
    }
    void div_num(vector<pii>&dv,int num)
    {
        int tot=0;
        while(num>=1000000) 
        {
            int mxfc=por::mian(num);
            tot=0;
            while(num%mxfc==0) tot++,num/=mxfc;
            dv.pb(mkp(mxfc,tot));
        }
        if(!tot_pri) euler_pri();
        R(i,1,tot_pri) 
        {
            if(pri[i]>num) break;
            if(num%pri[i]==0) 
            {
                tot=0;
                while(num%pri[i]==0) tot++,num/=pri[i];
                dv.pb(mkp(pri[i],tot));
            }
        }
        if(num^1) dv.pb(mkp(num,1));
        sort(dv.begin(),dv.end());
    }
    int get_ord(int p,int phi,vector<pii>&dv)
    {
        for(int k=2;;k++)
        {
            int ok=1;
            R(i,0,(int)dv.size()-1) if(qpow(k,phi/dv[i].fi,p)==1) {ok=0;break;}
            if(ok) return k;
        }
    }
    int BSGS(int a,int b,int p2,int p)
    {
        a%=p,b%=p;
        if(b==1) return 0;
        if(!a) {if(!b) return 1;return -1;}
        mp.clear();
        int sq,mu=1,ff=get_inv(a,p);
        for(sq=1;sq*sq<=p2;sq++);
        R(i,0,sq-1) mp[mu*b%p]=i,mu=mu*ff%p; //mp.ins(mu*b%p,i),mu=mu*ff%p;
        ff=get_inv(mu,p);mu=1;
        R(i,0,sq-1)
        { 
            /*
            int pos=mp.ser(mu);
            if(pos!=-1) return i*sq+pos;
            */
            if(mp.count(mu)) return i*sq+mp[mu];
            mu=mu*ff%p;
        }
        return -1;
    }
    int solve_exbsgs(int a,int b,int p2,int p)
    {
        if(a==b) return 1;
        int d=gcd(a,p);
        if(d==1) return BSGS(a,b,p2,p);
        if(b%d) return -1;
        p/=d;
        return solve_exbsgs(a%p,(b/d)*get_inv(a/d,p)%p,p2,p)+1;
    }
    int exBSGS(int a,int b,int p2,int p)
    {
        a%=p,b%=p;
        if(b==1) return 0;
        if(!a) return !b?1:-1;
        return solve_exbsgs(a,b,p2,p);
    }
    int getv(int a,int b,int p2,int tot,int p)
    {
        vector<int>pi;
        int pwp=1;
        R(i,0,tot) pi.pb(pwp),pwp*=p2;
        int buf=qpow(a,pi[tot-1],p),inv=0,tx;pwp=1;
       // printf("acc%lld\n",buf);
        L(i,0,tot-1)
        {       
            tx=pwp*BSGS(buf,qpow((lll)b*qpow(a,pi[tot]-inv,p)%p,pi[i],p),p2,p);
            //printf("tx:%lld\n",tx);
            inv+=tx;pwp*=p2; 
        }
        return inv;
    }
    int CRT(vector<int>&a,vector<pii>&dv)
    {
        int szd=dv.size();
        vector<int>b;
        int M=1,ans=0,Mi;
        R(i,0,szd-1) b.pb(qpow(dv[i].fi,dv[i].se)),M*=b[i];
        R(i,0,szd-1) Mi=M/b[i],ans=((lll)ans+(lll)Mi*get_inv(Mi,b[i])*a[i])%M;
        if(ans<0) ans+=M;
        return ans;
    } 
    int EXCRT(vector<int>&a,vector<pii>&dv)
    {
        int szd=dv.size();
        vector<int>b;
        R(i,0,szd-1) b.pb(qpow(dv[i].fi,dv[i].se));
        int M=b[0],ans=a[0];
        int x,y;
        int A,B,C,D,BG;
        R(i,1,szd-1) 
        {
            A=M,B=b[i],C=(a[i]-ans%B+B)%B;
            D=exgcd(A,B,x,y),BG=B/D;
            if(C%D) return -1;
            x=(lll)x*(C/D)%BG;
            ans+=x*M;
            M*=BG;
            ans=(ans%M+M)%M;
        }
        return ans;
    }
    int getx(int a,int b,int phiP,int p,vector<pii>&dv)
    {
        vector<int>v;
        for(auto qwq:dv) 
        {
            int fq=qpow(qwq.fi,qwq.se),pf=phiP/fq;
            int ta=qpow(a,pf,p),tb=qpow(b,pf,p);
            int tmp;
            v.pb(tmp=getv(tb,ta,qwq.fi,qwq.se,p));
        }
        return EXCRT(v,dv);
    }
    int solve_exgcd(int a,int b,int c)
    {
        int d=gcd(a,b),x=0,y=0;
        if(c%d) return -1;
        a/=d,b/=d,c/=d;
        exgcd(a,b,x,y);
        x=(lll)x*c%b;
        while(x<0) {x+=b;}
        return x;
    }
    vector<pii>dv;
    int phiP,yg;
    void init(int p) 
    {
        phiP=p-1;
        div_num(dv,phiP);
        yg=get_ord(p,phiP,dv);
    }
    int mian(int a,int b,int p)//p为素数的情况 
    {
        if(!b) return 0;
        int x=getx(a,yg,phiP,p,dv);
        int y=getx(b,yg,phiP,p,dv);
        if(!x) {if(!y) return 1;if(y==1) return 0;return -1;}
        return solve_exgcd(x,phiP,y);
    }
}
signed main()
{
    //freopen("test.out","w",stdout);
    int _=read(),p=read(),a,b,ans;
    DLP::init(p);
    for(;_;_--)
    {
        a=read(),b=read();
        ans=DLP::mian(a,b,p);
        printf(ans<0?"-1\n":"%lld\n",ans);
    }
    
}