写这两个东西是因为 SoyTony 把它们放到一起写的 .
Baby Step Gaint Step
实际上 BSGS 可能是指一类思想,即以 \(\sqrt V\) 为块长分块,\(V\) 是值域,这么写出来之后似乎看起来并不困难啊 .
这个想法,EI 有一个在数据结构方面的应用,或许叫整体分块吧?脑洞:整体分块 + BSGS .
离散对数问题
BSGS 思想最广为人知的应用就是对于离散对数问题的求解了,问题描述如下:
Discrete Logging I
给素数 \(p\) 和整数 \(a,n\),输出以下方程的最小解:
\[a^x\equiv n\pmod p \]
令块长 \(B=\lceil\sqrt p\rceil\),令 \(x=kB+r\),则
\[a^{kB}\equiv n\cdot a^{-r}\pmod p \]左右均只有 \(\Theta(\sqrt p)\) 种取值,把右边的所有取值处理后扔 Hash Table 里找就行了,时间复杂度 \(\Theta(\sqrt p)\) .
正确性保证是 \(a^{-r}\) 存在,于是 \(a\perp p\) 时即正确 .
注意由于 Hash Table 加成所以做 \(T\) 次 BSGS 的时间复杂度是 \(\Theta(\sqrt{Tp})\) .
unordered_map<int, int> H;
inline int BSGS(int p, int b, int n)
{
if (b % p == 1) return 0;
if (!(b % p)) return -1;
int m = sqrt(p) + 1, base = n % p;
for (int i=0; i<=m; i++, base = 1ll * base * b % p) H[base] = i;
base = qpow(b, m, p);
int tmp = 1;
for (int i=1; i<=m; i++)
{
tmp = 1ll * tmp * base % p;
auto _ = H.find(tmp);
if (_ != H.end()) return i * m - _->second;
}
return -1;
}
Discrete Logging II
给整数 \(a,p,n\),输出以下方程的最小解:
\[a^x\equiv n\pmod p \]
也就是 exBSGS 啦 .
首先特判 \(n=1\) 情况,这个平凡 .
令 \(d=\gcd(a,p)\),则原式可以变为
\[\dfrac ad\cdot a^{x-1}\equiv\dfrac nd\pmod{\dfrac pd} \]如果 \(d\nmid n\) 那么无解 .
这样就得到一个子问题,反复应用直到底数和模数互素,此时使用 Discrete Logging I 的做法即可 .
光速幂
「光速幂」也是 BSGS 思想的一个经典应用,问题描述如下:
That Light
给整数 \(a,p\),\(q\) 组询问,每次给一个整数 \(n\),求 \(a^n\bmod p\) .
令 \(V=\varphi(p)\),块长 \(B = \lceil\sqrt{V}\rceil\) .
令 \(n=kB+r\),则 \(a^n=a^{kB}\cdot a^r\),乘号两边都只有 \(\Theta(\sqrt V)\) 种取值,预处理即可 .
时间复杂度 \(\Theta(\sqrt V+q)\) .
原根相关
定义
若整数 \(a,m\) 互素,则定义满足 \(a^n\equiv 1\pmod m\) 的最小正整数 \(n\) 称作 \(a\) 模 \(m\) 的阶,记作 \(\delta_m(a)\) 或者 \(\ord_ma\) .
若 \(a\perp m\) 且 \(\ord_ma=\varphi(m)\) 则称 \(a\) 为 \(m\) 的原根 .
令 \(\ind_g x\) 为满足 \(g^k=a\pmod m\) 且 \(0\le k<\varphi(m)\) 的 \(k\),称作 \(a\) 模 \(m\) 对原根 \(g\) 的指标 .
性质(阶):
- \(a^x\equiv 1\pmod m\iff\ord_m a\mid x\) .
- \(\ord_m a^k=\dfrac{\ord_m a}{\gcd(\ord_ma,k)}=\dfrac{\lcm(\ord_ma,k)}k\) .
性质(原根):
- 原根判定定理:对于 \(m\ge 3\),\(g\perp m\),\(g\) 是 \(p\) 的原根当且仅当对于任意 \(\varphi m\) 的素因子 \(p\) 均有 \(a^{\varphi(m)/p}\not\equiv 1\pmod m\) .
- 原根存在定理:\(m\) 存在原根当且仅当 \(m\) 形如 \(2,4,p^{\alpha},2p^{\alpha}\),其中 \(p\) 为奇素数 .
- 原根个数定理:若正整数 \(m\) 存在原根,则其个数为 \(\varphi(\varphi(m))\) .
- 整数 \(m\) 的最小原根 \(g\) 的级别为 \(g=O(m^{0.25+\varepsilon})\) .
证明略 .
一个不知道有啥用的事实:若 \(m\) 存在原根,则是的 \(\ord_ma=l\) 的 \(a\) 个数为 \(\varphi(l)[l\mid\varphi(m)]\) .
求解
根据以上性质可以尝试求出某整数的原根和阶,指标是 BSGS .
求阶也可以 BSGS,或者考虑对于 \(x=k\ord_ma\),必然有 \(a^x\equiv 1\pmod m\),那么可以先令 \(x=\varphi(m)\),然后依次除去 \(x\) 的每个素因子 \(p\) 直到 \(a^{x/p}\not\equiv 1\pmod m\),这样 \(x\) 就是原根了,这样时间复杂度是 \(\Theta(\factor(m)+\log^2m)\) 的 .
关于原根,考虑使用原根判定定理直接枚举最小原根 \(g\),显然可以在 \(O(\factor(m)+m^{0.25}\omega(m)\log m)\) 时间复杂度内求出最小原根 .
根据阶的性质 2,可以得到
\[\ord_m g^k=\dfrac{\ord_m g}{\gcd(\ord_m g,k)}=\dfrac{\varphi(m)}{\gcd(\varphi(m),k)} \]那么只要 \(k\mid\varphi(m)\),则 \(g^k\) 也是 \(m\) 的原根,于是直接枚举 \(k\) 求即可 .
洛谷模板(找所有原根)
const int N = 3919810;
inline int qpow(int a, int n, int p)
{
int ans = 1;
while (n)
{
if (n & 1) ans = 1ll * ans * a % p;
a = 1ll * a * a % p; n >>= 1;
} return ans;
}
int gcd(int a, int b){return b ? gcd(b, a % b) : a;}
bool notprime[N], vaild[N];
int phi[N];
vector<int> prime;
inline void linear_sieve(int n)
{
notprime[1] = true; phi[1] = 1;
for (int i=2; i<=n; i++)
{
if (!notprime[i]){prime.emplace_back(i); phi[i] = i-1;}
for (auto x : prime)
{
int now = i*x;
if (now > n) break;
notprime[now] = true;
if (!(i%x)){phi[now] = phi[i] * x; break;}
else phi[now] = phi[i] * phi[x];
}
}
vaild[2] = vaild[4] = true;
for (int p : prime)
{
if (p == 2) continue;
for (int i=1; 1ll*i*p<=n; i*=p)
{
vaild[i*p] = true;
if (2ll*i*p <= n) vaild[2*i*p] = true;
}
}
}
inline int findroot(int x)
{
int p = x, h = phi[p]; x = h;
vector<int> factor;
for (int d : prime)
{
if (d * d >= x) break;
if (!(x % d))
{
factor.emplace_back(d);
while (!(x % d)) x /= d;
}
}
if (x != 1) factor.emplace_back(x);
for (int g=1; ; g++)
{
if (qpow(g, h, p) != 1) continue;
bool ok = true;
for (int d : factor)
if (qpow(g, h / d, p) == 1){ok = false; break;}
if (ok) return g;
}
}
int p, d;
vector<int> ans;
int main()
{
int T; scanf("%d", &T); linear_sieve(1e6);
while (T--)
{
scanf("%d%d", &p, &d); ans.clear();
if (!vaild[p]){puts("0\n"); continue;}
int g = findroot(p), now = 1, c;
for (int i=1; i<=phi[p]; i++)
{
now = 1ll * now * g % p;
if (gcd(i, phi[p]) == 1) ans.emplace_back(now);
}
stable_sort(ans.begin(), ans.end());
printf("%d\n", c = phi[phi[p]]);
for (int i=1; i<=c/d; i++) printf("%d ", ans[i * d - 1]);
puts("");
}
return 0;
}
应用
阶的主要应用是找 \(c^k\) 的循环节长度,这个比较无聊 .
如果你实在无聊可以看例题 .
Example
随机数
一个线性同余生成器由整数 \(x_0,n,c,a\) 描述,其生成的第 \(i\) 个随机数为 \(x_i\) 满足 \(x_i=(c\times x_{i-1} + a)\bmod n\),其中 \(i>0\) .
令生成的数列为 \(\{x\}\),定义对于整数 \(i\) 和 \(j\),有 \(0\leq i<j\) 且 \(x_i=x_j\),则称其为一个重复二元组 .
现给定线性同余生成器的四个参数 \(x_0,n,c,a\),你需要找到一个重复二元组,满足 \(i\) 尽可能小,在此基础上要求 \(j\) 尽可能小 .
写出序列 \(\{x\}\) 就是 \(x_k\equiv x_0c^k+a\sum_{i=0}^{k-1}c_i\pmod n\) .
首先特判 \(c=1\),那么根据等比数列求和就可以知道 \(x_k=\left(x_0c_k+a\cdot\frac{1-c^k}{1-c}\right)\bmod n\) .
我们希望 \(c\perp n\),这样就可以通过 ord 得到 \(c^k\) 的循环节 .
令 \(\gcd(c,n)=g\neq 1\),那么考虑对 \(x_i\) 作带余除法 \(x_i=(y_ig+a)\bmod n\),则可以得出 \(\{y\}\) 的递推 \(y_i=\left(c\cdot y_{i-1}+\frac{ca}g\right)\bmod \frac ng\) .
重复过程,即可得到 \(c\perp n\) 的子问题 .
接下来的一个障碍是 \(1-c\) 不一定存在逆元,这可以用类似的方法解决 .
令 \(\gcd(c-1,n)=g\neq 1\),那么 \(x_i\equiv x_{i-1}+a\pmod g\),则它的周期为 \(p=\dfrac{g}{\gcd(a,g)}\) .
那么对于所有 \(x_{pk}\),它们模 \(g\) 全部相等,那么对它作带余除法 \(x_{pi}=(y_ig+x_0)\bmod n\),则可以得到 \(\{y\}\) 的递推 \(y_i\equiv c^p\cdot y_{i-1}+\frac{(x_0-a)(c^p-1)}{(1-c)g}\pmod{\frac ng}\) 。
重复过程,即可得到 \(1-c\perp n\) 的子问题 .
这样考虑原始式子 \(x_k=\left(x_0c_k+a\cdot\frac{1-c^k}{1-c}\right)\bmod n\),那么令 \(v\equiv (x_0-\frac a{1-c})\pmod n\),于是只需要求 \(vc^k\) 在模 \(n\) 下的周期,这就是 \(\ord_{n/\gcd(v,n)}c\),于是这题就做完了 .
首先原根的一个应用是把乘法换成加法,因为原根可以用来生成模模数的缩系,也即 \(a\cdot b=g^{\ind_g a}\cdot g^{\ind_g b}=g^{\ind_g a+\ind_g b}\) .
由于原根的性质,模 \(m\) 意义下 \([0,m)\) 的指标都是存在且互不相同的 .
Discrete Root
给整数 \(a,k,p\),求 \(x^k\equiv a\pmod p\) 的所有根,\(p\) 是素数 .
首先求出 \(p\) 的原根 \(g\),那么 \(x^k\equiv a\pmod p\iff(g^{\ind x})^k\equiv a\pmod p\iff (g^k)^{\ind x}\equiv a\pmod p\) .
\(g^k\) 是定值,那么剩下的部分 BSGS 即可解决 .
小 A 与两位神仙
给整数 \(x,y,p\),问是否存在 \(a\) 使得 \(x^a\equiv y\pmod p\),保证 \(p\) 是奇素数的正整数次幂,\(x,y\) 均与 \(p\) 互素 .
首先用原根就可以把原式改成 \(\ind_g x\cdot a\equiv\ind_g y\pmod{\varphi(p)}\) .
根据 Bézout 定理,有解当且仅当 \(\gcd(\ind_g x,\varphi(p))\mid\ind_g y\),又等价于 \(\gcd(\ind_g x,\varphi(p))\mid\gcd(\ind_g y,\varphi(p))\) .
根据阶的性质 2,可以改写为 \(\ord_p x=\dfrac{\varphi(p)}{\gcd(\varphi(p),\ind_g x)}\),那么又可以改写为 \(\ord_p y\mid \ord_p x\) .
然后求出阶来判断即可 .
同样结论题:ABC212G Power Pair .
D
维护序列 \(\{a_n\}\),有素数 \(p\) 是定值,支持:
1 l r x
,对于所有 \(i\in[l,r]\),令 \(a_i\gets a_i\cdot x\) .2 l r
,询问序列区间 \([l,r]\) 的元素去重后构成的不可重集合的权值,其中对于集合 \(S\),定义集合 \(T\) 为满足 \(S\subset T\) 且 \(\forall x,y\in T\),\(xy\bmod p\in T\) 的最小集合,此时称 \(|T|\)(\(T\) 的大小) 为 \(S\) 的权值 .
首先用原根变成加,那么权值的大小就是 Bézout 定理,可以知道
\[\operatorname{val}(S)=\dfrac{p-1}{\gcd(\gcd_{x\in S}\ind_g x),p-1} \]然后就是区间加区间 GCD 了,这是经典问题,差分后线段树维护即可 .
根据分析可以得到时间复杂度是 \(O(\sqrt p+\sqrt{(n+q)p}+(n+q)\log p+q\log n)\)(朴素实现 —— 分解质因数使用根号试除,求解指标使用朴素 BSGS),这都啥 .
另外一个原根的应用是代替单位根,有性质:若 \(\varphi(n)\) 有原根 \(g\),则 \(\varphi(n)\) 的简化剩余系与 \(n\) 次单位根同构,有点显然 .
这里就没有例题了,因为主要部分是单位根,和原根没太大关系 .
标签:原根,int,varphi,pmod,ord,BSGS,equiv From: https://www.cnblogs.com/CDOI-24374/p/17189630.html