BSGS 和原根
写这两个东西是因为 SoyTony 把它们放到一起写的 .
Baby Step Gaint Step
实际上 BSGS 可能是指一类思想,即以 为块长分块, 是值域,这么写出来之后似乎看起来并不困难啊 .
这个想法,EI 有一个在数据结构方面的应用,或许叫整体分块吧?脑洞:整体分块 + BSGS .
离散对数问题
BSGS 思想最广为人知的应用就是对于离散对数问题的求解了,问题描述如下:
Discrete Logging I
给素数 和整数 ,输出以下方程的最小解:
令块长 ,令 ,则
左右均只有 种取值,把右边的所有取值处理后扔 Hash Table 里找就行了,时间复杂度 .
正确性保证是 存在,于是 时即正确 .
注意块长调的好的话做 次 BSGS 的时间复杂度是 的 .
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
给整数 ,输出以下方程的最小解:
也就是 exBSGS 啦 .
首先特判 情况,这个平凡 .
令 ,则原式可以变为
如果 那么无解 .
这样就得到一个子问题,反复应用直到底数和模数互素,此时使用 Discrete Logging I 的做法即可 .
光速幂
「光速幂」也是 BSGS 思想的一个经典应用,问题描述如下:
That Light
给整数 , 组询问,每次给一个整数 ,求 .
令 ,块长 .
令 ,则 ,乘号两边都只有 种取值,预处理即可 .
时间复杂度 .
原根相关
定义
若整数 互素,则定义满足 的最小正整数 称作 模 的阶,记作 或者 .
若 且 则称 为 的原根 .
令 为满足 且 的 ,称作 模 对原根 的指标 .
性质(阶):
- .
- .
性质(原根):
- 原根判定定理:对于 ,, 是 的原根当且仅当对于任意 的素因子 均有 .
- 原根存在定理: 存在原根当且仅当 形如 ,其中 为奇素数 .
- 原根个数定理:若正整数 存在原根,则其个数为 .
- 整数 的最小原根 的级别为 .
证明略 .
一个不知道有啥用的事实:若 存在原根,则使得 的 个数为 .
求解
根据以上性质可以尝试求出某整数的原根和阶,指标是 BSGS .
求阶也可以 BSGS,或者考虑对于 ,必然有 ,那么可以先令 ,然后依次除去 的每个素因子 直到 ,这样 就是原根了,这样时间复杂度是 的 .
关于原根,考虑使用原根判定定理直接枚举最小原根 ,显然可以在 时间复杂度内求出最小原根 .
根据阶的性质 2,可以得到
那么只要 ,则 也是 的原根,于是直接枚举 求即可 .
洛谷模板(找所有原根)
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;
}
应用
阶的主要应用是找 的循环节长度,这个比较无聊 .
如果你实在无聊可以看例题 .
Example
随机数
一个线性同余生成器由整数 描述,其生成的第 个随机数为 满足 ,其中 .
令生成的数列为 ,定义对于整数 和 ,有 且 ,则称其为一个重复二元组 .
现给定线性同余生成器的四个参数 ,你需要找到一个重复二元组,满足 尽可能小,在此基础上要求 尽可能小 .
写出序列 就是 .
首先特判 ,那么根据等比数列求和就可以知道 .
我们希望 ,这样就可以通过 ord 得到 的循环节 .
令 ,那么考虑对 作带余除法 ,则可以得出 的递推 .
重复过程,即可得到 的子问题 .
接下来的一个障碍是 不一定存在逆元,这可以用类似的方法解决 .
令 ,那么 ,则它的周期为 .
那么对于所有 ,它们模 全部相等,那么对它作带余除法 ,则可以得到 的递推 。
重复过程,即可得到 的子问题 .
这样考虑原始式子 ,那么令 ,于是只需要求 在模 下的周期,这就是 ,于是这题就做完了 .
首先原根的一个应用是把乘法换成加法,因为原根可以用来生成模模数的缩系,也即 .
由于原根的性质,模 意义下 的指标都是存在且互不相同的 .
Discrete Root
给整数 ,求 的所有根, 是素数 .
首先求出 的原根 ,那么 .
是定值,那么剩下的部分 BSGS 即可解决 .
小 A 与两位神仙
给整数 ,问是否存在 使得 ,保证 是奇素数的正整数次幂, 均与 互素 .
首先用原根就可以把原式改成 .
根据 Bézout 定理,有解当且仅当 ,又等价于 .
根据阶的性质 2,可以改写为 ,那么又可以改写为 .
然后求出阶来判断即可 .
同样结论题:ABC212G Power Pair .
D
维护序列 ,有素数 是定值,支持:
1 l r x
,对于所有 ,令 .2 l r
,询问序列区间 的元素去重后构成的集合的权值,其中对于集合 ,定义集合 为满足 且 的最小集合,此时称 为 的权值 .
首先用原根变成加,那么权值的大小就是 Bézout 定理,可以知道
然后就是区间加区间 GCD 了,这是经典问题,差分后线段树维护即可 .
根据分析可以得到时间复杂度是 (朴素实现 —— 分解质因数使用根号试除,求解指标使用朴素 BSGS),这都啥 .
另外一个原根的应用是代替单位根,有性质:若 有原根 ,则 的简化剩余系与 次单位根同构,有点显然 .
这里就没有例题了,因为主要部分是单位根,和原根没太大关系 .
以下是博客签名,正文无关
本文来自博客园,作者:yspm,转载请注明原文链接:https://www.cnblogs.com/CDOI-24374/p/17189630.html
版权声明:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0)进行许可。看完如果觉得有用请点个赞吧 QwQ
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】