Appearance
由于“同余”的篇幅过大,模意义的一些基础概念单开一页。
阶:
若正整数
性质
在模 意义下两两不同
逆元:
若正整数
求解
求解逆元等价于解同余方程
时间复杂度:
预处理 个数的逆元
预处理 模质数 的逆元:
线性递推即可,
注:若
预处理任意 个数模质数 的逆元:
令
时间复杂度:
通常使用这种方式预处理阶乘和阶乘的逆元
在线逆元
本节统一使用 Barrett 结构体内部的成员 m 以及 init、inv 的参数 mod 均取值为 barret.init(p),再执行逆元表的 init(p)。
该方法只处理质数模数
大致做法是利用
恢复
具体地,将
当前固定 B = 300 的实现必须满足以下前提:
是质数,并且 。因此 B = 300只能直接覆盖,不能覆盖常见的 级模数。 res1、res2至少能访问到下标, iv、_iv至少能访问到下标,因此数组容量必须严格大于 。 - 当前
iv的线性递推只适用于。由于代码预处理到 ,还需保证 ;若不满足,应对该模数改用普通线性预处理、快速幂或扩展欧几里得算法。 B = 300时,这一条件是。 - 所有送入快速取模的乘积都必须先在
u64中计算,不能先以 32 位int相乘再隐式转换。当前代码涉及的中间量上界约为;它必须能放入 u64,与预计算倒数相乘后的结果必须能放入u128。若项目中的int是普通 32 位类型,应在每个乘法前显式转换为u64。
因此,在不增加其他回退分支时,固定 B = 300 的这一模板实际上只适用于满足
下方 Barrett 实现使用 u32 m 保存模数、u64 A 保存待约简整数、u128 保存预计算倒数及乘积。它不是任意范围的取模:当前没有额外的商修正步骤,因此调用 div(A) 时应保证 u32,并保证 A\times ivm 不会溢出 u128。在本节的固定参数范围内,送入 calc 的中间量满足
模数
点击展开代码
cpp
const int N = (1 << 21) | 1;
const int B = 300; // 需要满足 B^3 >= mod
int res1[N], res2[N], iv[N], _iv[N];
struct Barrett {
enum { s = 96 };
static constexpr u128 s2 = u128(1) << s;
u32 m;
u128 ivm;
void init(u32 m_) { m = (m_), ivm = ((s2 - 1) / m + 1); }
u32 div(u64 a) const { return a * ivm >> s; }
u32 calc(u64 a) const { return a - u64(div(a)) * m; }
} barret; // 顺带实现了 barrett 快速取模,不能处理模数为 2 的情况
void init(int mod) {
int lim = mod / B;
int _lim = -lim + mod;
for (int u = 1; u <= B; u++) {
int d = u * B;
int a = 0;
for (int i = 0; i <= lim; i++) {
if (a <= lim) {
res1[i] = u;
} else if (a >= _lim) {
res1[i] = -u;
} else {
int r = (_lim - a - 1) / d;
a += r * d;
i += r;
}
a += d;
if (a >= mod)
a -= mod;
}
}
for (int a = 0; a <= lim; a++)
res2[a] = barret.calc((a * B) * (res1[a] + mod));
iv[1] = 1;
for (int i = 2; i < 2 * B * B + 1; i++)
iv[i] = barret.calc(iv[mod % i] * (mod - mod / i));
for (int i = 1; i < 2 * B * B + 1; i++)
_iv[i] = mod - iv[i];
}
int inv(int x, int mod) {
if (mod < B)
return iv[x];
int a = x / B, b = x % B;
int u = res1[a];
int v = (res2[a] + b * u);
if (v < 0)
return barret.calc((u + mod) * _iv[-v]);
return barret.calc((u + mod) * iv[v]);
}
barret.init(p);
init(p);