BSGS 和原根

写这两个东西是因为 SoyTony 把它们放到一起写的 .

\[\newcommand{\lcm}[0]{\operatorname{lcm}} \newcommand{\ord}[0]{\operatorname{ord}} \newcommand{\ind}[0]{\operatorname{ind}} \newcommand{\factor}[0]{\operatorname{factor}} \]

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\) 时即正确 .

注意块长调的好的话做 \(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\) 的指标 .

性质(阶):

  1. \(a^x\equiv 1\pmod m\iff\ord_m a\mid x\) .
  2. \(\ord_m a^k=\dfrac{\ord_m a}{\gcd(\ord_ma,k)}=\dfrac{\lcm(\ord_ma,k)}k\) .

性质(原根):

  1. 原根判定定理:对于 \(m\ge 3\)\(g\perp m\)\(g\)\(p\) 的原根当且仅当对于任意 \(\varphi(m)\) 的素因子 \(p\) 均有 \(a^{\varphi(m)/p}\not\equiv 1\pmod m\) .
  2. 原根存在定理:\(m\) 存在原根当且仅当 \(m\) 形如 \(2,4,p^{\alpha},2p^{\alpha}\),其中 \(p\) 为奇素数 .
  3. 原根个数定理:若正整数 \(m\) 存在原根,则其个数为 \(\varphi(\varphi(m))\) .
  4. 整数 \(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\subseteq T\)\(\forall x,y\in T,\ xy\bmod p\in 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\) 次单位根同构,有点显然 .

这里就没有例题了,因为主要部分是单位根,和原根没太大关系 .

posted @ 2023-03-09 11:55  Jijidawang  阅读(65)  评论(1编辑  收藏  举报
😅​