离散对数 & 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\) 。
- \(\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)}\) 。 - 若 \(g'\) 也是模 \(m\) 的原根,则 \(\operatorname{ind}_ga\equiv\operatorname{ind}_{g'}a\cdot\operatorname{ind}_{g}g'\) 。
- \(a\equiv b\pmod m\Leftrightarrow\operatorname{ind}_ga=\operatorname{ind}_gb\) 。
BSGS
设 \(a,b,m\in\mathbf{N^*}\) ,且 \((a,m)=1\) ,求 \(x\) 满足:
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\) 满足:
方法一
因为 \(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}\) 。
易知:
由 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;
}