BSGS 和原根

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

Baby Step Gaint Step

实际上 BSGS 可能是指一类思想,即以 V 为块长分块,V 是值域,这么写出来之后似乎看起来并不困难啊 .

这个想法,EI 有一个在数据结构方面的应用,或许叫整体分块吧?脑洞:整体分块 + BSGS .

离散对数问题

BSGS 思想最广为人知的应用就是对于离散对数问题的求解了,问题描述如下:

Discrete Logging I

给素数 p 和整数 a,n,输出以下方程的最小解:

axn(modp)

令块长 B=p,令 x=kB+r,则

akBnar(modp)

左右均只有 Θ(p) 种取值,把右边的所有取值处理后扔 Hash Table 里找就行了,时间复杂度 Θ(p) .

正确性保证是 ar 存在,于是 ap 时即正确 .

注意块长调的好的话做 T 次 BSGS 的时间复杂度是 Θ(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,输出以下方程的最小解:

axn(modp)

也就是 exBSGS 啦 .

首先特判 n=1 情况,这个平凡 .

d=gcd(a,p),则原式可以变为

adax1nd(modpd)

如果 dn 那么无解 .

这样就得到一个子问题,反复应用直到底数和模数互素,此时使用 Discrete Logging I 的做法即可 .

光速幂

「光速幂」也是 BSGS 思想的一个经典应用,问题描述如下:

That Light

给整数 a,pq 组询问,每次给一个整数 n,求 anmodp .

V=φ(p),块长 B=V .

n=kB+r,则 an=akBar,乘号两边都只有 Θ(V) 种取值,预处理即可 .

时间复杂度 Θ(V+q) .

原根相关

定义

若整数 a,m 互素,则定义满足 an1(modm) 的最小正整数 n 称作 am 的阶,记作 δm(a) 或者 ordma .

amordma=φ(m) 则称 am 的原根 .

indgx 为满足 gk=a(modm)0k<φ(m)k,称作 am 对原根 g 的指标 .

性质(阶):

  1. ax1(modm)ordmax .
  2. ordmak=ordmagcd(ordma,k)=lcm(ordma,k)k .

性质(原根):

  1. 原根判定定理:对于 m3gmgp 的原根当且仅当对于任意 φ(m) 的素因子 p 均有 aφ(m)/p1(modm) .
  2. 原根存在定理:m 存在原根当且仅当 m 形如 2,4,pα,2pα,其中 p 为奇素数 .
  3. 原根个数定理:若正整数 m 存在原根,则其个数为 φ(φ(m)) .
  4. 整数 m 的最小原根 g 的级别为 g=O(m0.25+ε) .

证明略 .

一个不知道有啥用的事实:若 m 存在原根,则使得 ordma=la 个数为 φ(l)[lφ(m)] .

求解

根据以上性质可以尝试求出某整数的原根和阶,指标是 BSGS .

求阶也可以 BSGS,或者考虑对于 x=kordma,必然有 ax1(modm),那么可以先令 x=φ(m),然后依次除去 x 的每个素因子 p 直到 ax/p1(modm),这样 x 就是原根了,这样时间复杂度是 Θ(factor(m)+log2m) 的 .

关于原根,考虑使用原根判定定理直接枚举最小原根 g,显然可以在 O(factor(m)+m0.25ω(m)logm) 时间复杂度内求出最小原根 .

根据阶的性质 2,可以得到

ordmgk=ordmggcd(ordmg,k)=φ(m)gcd(φ(m),k)

那么只要 kφ(m),则 gk 也是 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;
}

应用

阶的主要应用是找 ck 的循环节长度,这个比较无聊 .

如果你实在无聊可以看例题 .

Example

随机数

一个线性同余生成器由整数 x0,n,c,a 描述,其生成的第 i 个随机数为 xi 满足 xi=(c×xi1+a)modn,其中 i>0 .

令生成的数列为 {x},定义对于整数 ij,有 0i<jxi=xj,则称其为一个重复二元组 .

现给定线性同余生成器的四个参数 x0,n,c,a,你需要找到一个重复二元组,满足 i 尽可能小,在此基础上要求 j 尽可能小 .

写出序列 {x} 就是 xkx0ck+ai=0k1ci(modn) .

首先特判 c=1,那么根据等比数列求和就可以知道 xk=(x0ck+a1ck1c)modn .

我们希望 cn,这样就可以通过 ord 得到 ck 的循环节 .

gcd(c,n)=g1,那么考虑对 xi 作带余除法 xi=(yig+a)modn,则可以得出 {y} 的递推 yi=(cyi1+cag)modng .

重复过程,即可得到 cn 的子问题 .

接下来的一个障碍是 1c 不一定存在逆元,这可以用类似的方法解决 .

gcd(c1,n)=g1,那么 xixi1+a(modg),则它的周期为 p=ggcd(a,g) .

那么对于所有 xpk,它们模 g 全部相等,那么对它作带余除法 xpi=(yig+x0)modn,则可以得到 {y} 的递推 yicpyi1+(x0a)(cp1)(1c)g(modng)

重复过程,即可得到 1cn 的子问题 .

这样考虑原始式子 xk=(x0ck+a1ck1c)modn,那么令 v(x0a1c)(modn),于是只需要求 vck 在模 n 下的周期,这就是 ordn/gcd(v,n)c,于是这题就做完了 .


首先原根的一个应用是把乘法换成加法,因为原根可以用来生成模模数的缩系,也即 ab=gindgagindgb=gindga+indgb .

由于原根的性质,模 m 意义下 [0,m) 的指标都是存在且互不相同的 .

Discrete Root

给整数 a,k,p,求 xka(modp) 的所有根,p 是素数 .

首先求出 p 的原根 g,那么 xka(modp)(gindx)ka(modp)(gk)indxa(modp) .

gk 是定值,那么剩下的部分 BSGS 即可解决 .

小 A 与两位神仙

给整数 x,y,p,问是否存在 a 使得 xay(modp),保证 p 是奇素数的正整数次幂,x,y 均与 p 互素 .

首先用原根就可以把原式改成 indgxaindgy(modφ(p)) .

根据 Bézout 定理,有解当且仅当 gcd(indgx,φ(p))indgy,又等价于 gcd(indgx,φ(p))gcd(indgy,φ(p)) .

根据阶的性质 2,可以改写为 ordpx=φ(p)gcd(φ(p),indgx),那么又可以改写为 ordpyordpx .

然后求出阶来判断即可 .

同样结论题:ABC212G Power Pair .

D

维护序列 {an},有素数 p 是定值,支持:

  • 1 l r x,对于所有 i[l,r],令 aiaix .
  • 2 l r,询问序列区间 [l,r] 的元素去重后构成的集合的权值,其中对于集合 S,定义集合 T 为满足 STx,yT, xymodpT 的最小集合,此时称 |T|S 的权值 .

首先用原根变成加,那么权值的大小就是 Bézout 定理,可以知道

val(S)=p1gcd(gcdxSindgx,p1)

然后就是区间加区间 GCD 了,这是经典问题,差分后线段树维护即可 .

根据分析可以得到时间复杂度是 O(p+(n+q)p+(n+q)logp+qlogn)(朴素实现 —— 分解质因数使用根号试除,求解指标使用朴素 BSGS),这都啥 .


另外一个原根的应用是代替单位根,有性质:若 φ(n) 有原根 g,则 φ(n) 的简化剩余系与 n 次单位根同构,有点显然 .

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

posted @   yspm  阅读(81)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
😅​
点击右上角即可分享
微信分享提示