离散对数 & BSGS 学习笔记

离散对数

离散对数的定义方式和对数类似。取有原根的正整数模数 \(m\) ,设其一个原根为 \(g\) 。对满足 \((a,m)=1\) 的整数 \(a\) ,我们知道必存在唯一的整数 \(0\leq k<\varphi(m)\) 使得 \(g^k\equiv a\pmod m\)

我们称这个 \(k\) 为以 \(g\) 为底,模 \(m\) 的离散对数,记作 \(k=\operatorname{ind}_g a\) ,在不引起混淆的情况下可记作 \(\operatorname{ind} a\)

看看有啥性质。

\(g\) 是模 \(m\) 的原根, \((a,m)=(b,m)=1\)

  1. \(\operatorname{ind}_g(ab)\equiv\operatorname{ind}_ga+\operatorname{ind}_gb\pmod{\varphi(m)}\)
    ,进而对于 \(n\in\mathbf{N}\)\(\operatorname{ind}_g(a^n)\equiv n\operatorname{ind}_ga\pmod{\varphi(m)}\)
  2. \(g'\) 也是模 \(m\) 的原根,则 \(\operatorname{ind}_ga\equiv\operatorname{ind}_{g'}a\cdot\operatorname{ind}_{g}g'\)
  3. \(a\equiv b\pmod m\Leftrightarrow\operatorname{ind}_ga=\operatorname{ind}_gb\)

BSGS

\(a,b,m\in\mathbf{N^*}\) ,且 \((a,m)=1\) ,求 \(x\) 满足:

\[a^x\equiv b\pmod m \]

BSGS 能够在 \(O(\sqrt m)\) 时间内求解 \(x\) 的值。

\(x=A\lceil\sqrt m\rceil-B\) ,其中 \(0\le A,B\le\lceil\sqrt m\rceil\) ,则有 \(a^{A\lceil\sqrt m\rceil-B}\equiv b\pmod m\) ,即 \(a^{A\lceil\sqrt m\rceil}\equiv ba^B\pmod m\)

可以枚举 \(ba^B\) 的值,然后再枚举 \(a^{A\lceil\sqrt m\rceil}\) ,这样就可以找出所有的 \(x\)

再看看下面的问题。

\(a,b\in\mathbf{N^*},p\in\mathbf{P}\) ,求 \(x\) 满足:

\[x^a\equiv b\pmod p \]

方法一

因为 \(p\) 为质数,所以必有原根 \(g\)

\(x=g^i\) ,则原式为 \((g^a)^i\equiv b\pmod p\)

直接使用 BSGS 求解即可,这样可以找出 \(x\) 的一个解,复杂度 \(O(\sqrt p)\)

方法二

仍令 \(x=g^i\) ,并且设 \(b=g^t\) ,则原式为 \(g^{ia}\equiv g^t\pmod p\)

对等式两边求离散对数,得 \(ia\equiv t\pmod{\varphi(p)}\)

可以通过 BSGS 求解 \(g^t\equiv b\pmod p\) 得到 \(t\) ,于是转化为同余方程,可以找出 \(x\) 的一个解。

找到所有解

在得到特解 \(x_0\) 的情况下,我们想求出所有解。

我们知道 \(g^{\varphi(p)}\equiv1\pmod p\) ,则可知 \(x^a\equiv x_0^a\cdot g^{k\varphi(p)}\pmod p\)

于是我们得到 \(x\equiv x_0\cdot g^{k\varphi(p)/a}\pmod p\)

\(k=\dfrac{a}{(a,\varphi(p))}\cdot i\) ,得到 \(x\equiv x_0\cdot g^{\varphi(p)/(a,\varphi(p))}\)

题目

题单: https://vjudge.net/article/5400

洛谷 P3846 [TJOI2007] 可爱的质数/【模板】BSGS

模板。

#include <cmath>
#include <cstdio>
#include <map>
using namespace std;
typedef long long ll;
const ll inf = 0x3f3f3f3f3f3f3f3f;
void read(ll &x) {
    char c = getchar();
    ll v = 0, f = 1;
    while (c < '0' || '9' < c) {
        if (c == '-')
            f = -1;
        c = getchar();
    }
    while ('0' <= c && c <= '9') {
        v = (v << 1) + (v << 3) + (c ^ 48);
        c = getchar();
    }
    x = v * f;
}
ll p, a, b, m, ans = inf;
map<ll, ll> mp;
ll qpow(ll x, ll y) {
    ll z = 1;
    while (y) {
        if (y & 1)
            z = z * x % p;
        x = x * x % p;
        y >>= 1;
    }
    return z;
}
int main() {
    read(p), read(a), read(b);
    m = ceil(sqrt(p));
    for (ll i = 0; i <= m; i++) {
        ll x = qpow(a, i) * b % p;
        mp[x] = i;
    }
    for (ll i = 0; i <= m; i++) {
        ll x = qpow(a, i * m);
        if (mp[x])
            ans = min(ans, i * m - mp[x]);
    }
    if (ans == inf)
        puts("no solution");
    else
        printf("%lld\n", ans);
    return 0;
}

洛谷 P8450 [LSOT-1] 记忆崩塌

\(m=998244353,f_n=\sum^n_{i=1}\gcd(n,i)\)\(n\) 的唯一分解形式为 \(\prod p^k\)

易知 \(\gcd(n,i)|n\)

又易知 \(\forall\gcd(\frac{n}{j},i)=1,\gcd(n,i)=j\)

所以 \(f_n=\sum_{j|n}\varphi(\frac{n}{j})j\)

因为 \(\varphi,j\) 均为积性函数,所以 \(f\) 也为积性函数。

\(f_n=\prod f_{p^k}\)

易知:

\[\begin{aligned} f_{p^k}&=\varphi(p^k)+\varphi(p^{k-1})p+\dots+p^k\\&=(p-1)p^{k-1}+(p-1)p^{k-1}+\dots+p^k\\&=k(p-1)p^{k-1}+p^k \end{aligned}\]

GetGCD 操作可得到 \(p^k\bmod m\) ,使用 BSGS 可求出 \(k\)

通过计算(你也可以说是打表……)可知前 \(1000\) 个质数的阶都大于 \(10000\) ,所以 \(k\) 唯一。

\(n=1000,k=10000\) 时间复杂度 \(O(n\sqrt m+n\log k)\) ,可以通过此题。

#include <cmath>
#include <iostream>
#include <unordered_map>
using namespace std;
typedef long long ll;
const ll mod = 998244353;
ll m, ans = 1;

ll qpow(ll x, ll y) {
    ll z = 1;
    while (y) {
        if (y & 1)
            z = z * x % mod;
        x = x * x % mod;
        y >>= 1;
    }
    return z;
}

const ll N = 7919;
bool np[N + 5];
ll pri[N + 5], cnt = 0;
void init() {
    m = ceil(sqrt(mod));
    for (ll i = 2; i <= N; i++) {
        if (!np[i])
            pri[++cnt] = i;
        for (ll j = 1; j <= cnt; j++) {
            if (i * pri[j] > N)
                break;
            np[i * pri[j]] = true;
            if (i % pri[j] == 0)
                break;
        }
    }
}

unordered_map<ll, ll> mp;
ll BSGS(ll a, ll b) {
    mp.clear();
    ll x = b;
    for (ll i = 0; i <= m; i++) {
        mp[x] = i + 1;
        x = x * a % mod;
    }
    for (ll i = 0; i <= m; i++) {
        x = qpow(a, i * m);
        if (mp[x])
            return i * m - mp[x] + 1;
    }
    return -1;
}
int main() {
    init();
    for (ll i = 1; i <= cnt; i++) {
        ll x, p = pri[i], k;
        cout << "GetGCD. 1" << endl;
        cout << p << " 10000" << endl;
        cin >> x;
        if (x == 1)
            continue;
        k = BSGS(p, x);
        ll y1 = k * (p - 1) % mod * qpow(p, k - 1) % mod;
        ll y2 = qpow(p, k);
        ans = ans * (y1 + y2) % mod;
    }
    cout << "IFoundTheAnswer! " << ans << endl;
    return 0;
}
posted @ 2024-07-28 11:21  01bit  阅读(6)  评论(1编辑  收藏  举报