1. 程式人生 > >【離散對數 && EXBSGS】Gym

【離散對數 && EXBSGS】Gym

Step1 Problem:

a^x ≡ b(mod m).
給你 a, b, m, 求 x.
資料範圍:
1<=T<=500, 0 <= a, b < m <= 1e9.

Step2 Ideas:

學習演算法部落格
說說自己的理解:
a^x ≡ b(mod m).

a 和 m 互質:
根據尤拉定理:如果 gcd(a, m) == 1, a^phi(m) ≡ 1(mod m).
所以 a 和 m 互質,a^x mod m 隔 phi(m) 次一定存在週期。
a^x ≡ b(mod m).
x = b * s + r:需要用逆元
由於 phi(m) < m, 所以我們令 s = sqrt(m), 列舉 x = [0, s) 求出 a^x 用 map 存起來。
當 xi = [i * s+1, (i+1) * s) 時:xi = i * s + x, a^xi ≡ b(mod m) -> a^x ≡ b * a^(-i * s)(mod m).
此時:用 map 判斷 b * a^(-i * s) 是否存在,如果存在則有解。
直到 xb = [b * s+1, (b+1) * s) 為止。
x = b * s - r:不需要用逆元


由於程式碼採用是不用逆元的方法,所以直接看程式碼即可。

a 和 m 不互質:
我們需要把方程轉換成,a 和 m 互質。
a^x ≡ b mod m -> a^x + y*m = b.
由裴蜀定理,g = gcd(a, m) 不整除 b 那麼無解返回 -1.
否則:a/g * a^(x-1) + m/g * y = b/g.
模方程:a/g * a^(x-1) ≡ b/g (mod m/g).
令 m1 = m/g, b1 = b/g * (a/g)^(-1),得到新方程
a^x1 ≡ b1(mod m1).
可知:x = x1+1.
由於 a 是不變的,不斷重複上述操作,直到 a 和 m1 互質
如果 b1 = 1, 此時存在解 x1 = 0. 在求出 x 即可

Step3 Code:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll log_ab(ll a, ll b, ll MOD)
{
    a = a%MOD; b = b%MOD;
    if(b == 1) return 0;
    int cnt = 0;
    ll t = 1;
    for(ll g = __gcd(a, MOD); g != 1; g = __gcd(a, MOD))
    {
        if(b%g) return -1;//由裴蜀定理,可知無解
        MOD /= g, b /= g;
        t = t * a / g % MOD;
        cnt++;//記錄 x -> x1 經過了幾次。
        if(t == b) return cnt;//b1 = 1.
    }

    unordered_map<ll, ll> mp;
    int m = ceil(sqrt(1.0 * MOD));
    ll e = 1;
    for(int i = 0; i < m; i++) {//將 a^i 存起來,i = [0, sqrt(m)),如果不用逆元需要將 b*a^i 存起來。
       mp[e*b%MOD] = i;
       e = e * a % MOD;
    }
    // e  = a^(sqrt(m))
    ll nw = t;
    for(int i = 1; i <= m + 1; i++) {
        nw = e * nw % MOD;
        if(mp.count(nw)) {
            return i * m - mp[nw] + cnt;
        }
    }
    return -1;
}
int main()
{
    int T, a, b, m;
    scanf("%d", &T);
    while(T--)
    {
        scanf("%d %d %d", &a, &b, &m);
        ll x = log_ab(a, b, m);
        if(x == -1) ;
        else printf("%lld\n", x);
    }
    return 0;
}