[Some Tricks] 自动取模类
1. 源码
typedef long long i64;
typedef unsigned long long u64;
typedef __int128 i128;
const i128 o = 1;
template < i64 mod, i64 invpow = mod - 2 >
struct Modular {
u64 M = (o << 64) / mod;
i64 query (i64 x) {
u64 x_ = 1ull * x;
u64 q = 1ull * (((i128) (M) * (i128) (x_)) >> 64);
u64 r = x_ - q * (1ull * mod);
return r >= mod ? 1ll * r - mod : 1ll * r;
}
i64 x;
i64 norm (i64 v) {v = (v < 0) ? (v + mod) : v, v = (v >= mod) ? (v - mod) : v, v = (v >= mod) ? query (v) : v; return v;}
i64 pow_mod (i64 a, i64 b) {i64 res = 1; for (i64 i = b; i > 0; i >>= 1, a = norm (a * a)) res = (i & 1) ? norm (res * a) : res; return res;}
void init (i64 rhs) {x = norm (rhs); return ;}
i64 val () {return norm (x);}
void neg () {x = norm (mod - x); return ;}
Modular inv () {assert (x != 0); auto res = *this; res.x = pow_mod (res.x, invpow); return res;}
void add () {x = norm (x + 1); return ;}
void sub () {x = norm (x - 1); return ;}
Modular &operator += (Modular rhs) & {x = norm (x + rhs.x); return *this;}
Modular &operator -= (Modular rhs) & {x = norm (x - rhs.x); return *this;}
Modular &operator *= (Modular rhs) & {x = norm (x * rhs.x); return *this;}
Modular &operator /= (Modular rhs) & {*this *= rhs.inv (); return *this;}
Modular &operator += (i64 rhs) & {rhs = norm (rhs), x = norm (x + rhs); return *this;}
Modular &operator -= (i64 rhs) & {rhs = norm (rhs), x = norm (x - rhs); return *this;}
Modular &operator *= (i64 rhs) & {rhs = norm (rhs), x = norm (x * rhs); return *this;}
Modular &operator /= (i64 rhs) & {rhs = norm (rhs), *this *= pow_mod (rhs, invpow); return *this;}
Modular &operator ^= (i64 ind) & {x = pow_mod (x, ind), x = (ind < 0) ? pow_mod (x, invpow) : x; return *this;}
friend Modular operator + (Modular lhs, Modular rhs) {auto res = lhs; res += rhs; return res;}
friend Modular operator - (Modular lhs, Modular rhs) {auto res = lhs; res -= rhs; return res;}
friend Modular operator * (Modular lhs, Modular rhs) {auto res = lhs; res *= rhs; return res;}
friend Modular operator / (Modular lhs, Modular rhs) {auto res = lhs; res /= rhs; return res;}
friend Modular operator ^ (Modular lhs, i64 ind) {auto res = lhs; res ^= ind; return res;}
friend bool operator == (Modular lhs, Modular rhs) {return (lhs.val () == rhs.val ());}
friend bool operator != (Modular lhs, Modular rhs) {return (lhs.val () != rhs.val ());}
friend bool operator > (Modular lhs, Modular rhs) {return (lhs.val () > rhs.val ());}
friend bool operator < (Modular lhs, Modular rhs) {return (lhs.val () < rhs.val ());}
friend bool operator >= (Modular lhs, Modular rhs) {return (lhs.val () >= rhs.val ());}
friend bool operator <= (Modular lhs, Modular rhs) {return (lhs.val () <= rhs.val ());}
};
template < i64 mod, i64 invpow = mod - 2 >
Modular < mod, invpow > addmod (Modular < mod, invpow > lhs, i64 rhs) {
rhs = lhs.norm (rhs);
lhs.x = lhs.norm (lhs.x + rhs);
return lhs;
}
template < i64 mod, i64 invpow = mod - 2 >
Modular < mod, invpow > submod (Modular < mod, invpow > lhs, i64 rhs) {
rhs = lhs.norm (rhs);
lhs.x = lhs.norm (lhs.x - rhs);
return lhs;
}
template < i64 mod, i64 invpow = mod - 2 >
Modular < mod, invpow > mulmod (Modular < mod, invpow > lhs, i64 rhs) {
rhs = lhs.norm (rhs);
lhs.x = lhs.norm (lhs.x * rhs);
return lhs;
}
template < i64 mod, i64 invpow = mod - 2 >
Modular < mod, invpow > divmod (Modular < mod, invpow > lhs, i64 rhs) {
rhs = lhs.norm (rhs);
Modular < mod, invpow > it;
it.init (rhs);
lhs.x = lhs.norm (lhs.x * it.val ());
return lhs;
}
2. 使用说明
2.1 定义一个 Modular
2.1.1 定义过程
定义一个自动取模类可以写:Modular < mod, invpow >
,其中 \(mod\) 为 模数,\(invpow = \varphi(mod) - 1\)。
可以证明对于任意的 \(x\) 满足 \(\gcd(x, mod) = 1\),有:
根据欧拉定理,有对于任意的 \(x\) 满足 \(\gcd(x, mod) = 1\),有:
等式两边各乘 \(x^{-1}\),有:
得证。
因为在 OI 中模数大部分是质数,进而有 \(\varphi(mod) = mod - 1\),即 \(invpow = mod - 2\),所以我们 默认 \(invpow = mod - 2\)。(即如果写 Modular < mod >
的话 \(invpow\) 就默认为 \(mod - 2\))
2.1.2 时间复杂度
\(O(1)\)。
2.2 query
函数
注:此函数在一般情况下不直接使用。
此函数的实现基于快速取模。
2.2.1 用途
query
函数可用于卡常数,且有 \(\text{query}(x) = x \bmod mod\)。
2.2.2 预处理
定义 \(M = \left\lfloor \dfrac{2^{64}}{mod} \right\rfloor\)。
2.2.3 函数原理
对于每一个 \(x\),我们要返回 \(x \bmod mod\)。
设 \(q = \left\lfloor \dfrac{M \times x}{2^{64}} \right\rfloor, r = x - q \times mod\)。则有:
考虑证明:
考虑带入 \(M\),那么有:
带入 \(q\),有:
因为 \(r\) 是 \(x\) 减去一个 \(mod\) 的倍数,所以 \(r \bmod mod = x \bmod mod\)。
因为 \(\left\lfloor \dfrac{x}{mod} - \dfrac{(2^{64} \bmod mod) \times x}{2^{64} \times mod} \right\rfloor \leq \left\lfloor \dfrac{x}{mod} \right\rfloor\),所以 \(r \geq x - \left\lfloor \dfrac{x}{mod} \right\rfloor \times mod\),即 \(r \geq x \bmod mod\)。
所以 \(r\) 一定可以写成 \(x \bmod mod + \epsilon \times mod\) 的形式。
显然 \(\epsilon\) 一定是一个整数,且下界为 \(0\)。
下求 \(\epsilon\) 的上界。
因为原函数中的 x
是 long long
类型的,所以有 \(x < 2^{64}\)。显然有 \(2^{64} \bmod mod < mod\),所以有:
所以有 \(\left\lfloor \dfrac{x}{mod} \right\rfloor - 1 \leq \left\lfloor \dfrac{x}{mod} - \dfrac{(2^{64} \bmod mod) \times x}{2^{64} \times mod} \right\rfloor \leq \left\lfloor \dfrac{x}{mod} \right\rfloor\)。
故 \(\epsilon\) 的上界为 \(1\),所以有 \(0 \leq \epsilon \leq 1\)。
所以得到的 \(r\) 至多减去 \(\symbfit{1}\) 次 \(\symbfit{mod}\) 即可得到正确的值。
得证。
2.2.4 时间复杂度
\(O(1)\)。
2.3 norm
函数
2.3.1 用途
调用 \(\text{norm}(x)\),可以得到 \(x \bmod mod\)。
2.3.2 函数原理
如果 \(x\) 减去 \(1\) 次 \(mod\) 可以满足要求就直接减。如果 \(x < 0\) 就加上一次 \(mod\)。否则调用 query
函数。
2.3.3 正确性
此函数在 Modular
类其他运算及时取模的前提下有正确性。
2.3.4 时间复杂度
\(O(1)\)。
2.4 pow_mod
函数
2.4.1 用途
调用 \(\text{pow_mod}(a, b)\) 可以得到 \(a^b \bmod mod\) 的值。
2.4.2 函数原理
与快速幂原理相同。如果不知道快速幂的原理请回普及组重学。
2.4.3 时间复杂度
\(O(\log b)\)。
2.5 init
函数
2.5.1 用途
调用 \(\text{init}(rhs)\) 可以将此类对应的值赋值为 \(rhs\)。
2.5.2 时间复杂度
\(O(1)\)。
2.6 val
函数
2.6.1 用途
调用 \(\text{val}()\) 可以返回此类对应的值(long long
)。
2.6.2 时间复杂度
\(O(1)\)。
2.7 neg
函数
2.7.1 用途
调用 \(\text{neg}()\) 可以将此类对应的值乘上 \(\symbfit{-1}\)。
2.7.2 时间复杂度
\(O(1)\)。
2.8 inv
函数
2.8.1 用途
设此类对应的值为 \(x\),则调用 \(\text{inv}()\) 可以返回 \(x^{-1}\)。
2.8.2 时间复杂度
\(O(\log mod)\)。
2.9 add
函数
2.9.1 用途
调用 \(\text{add}()\) 可以将类对应的值加上 \(1\)。
2.9.2 时间复杂度
\(O(1)\)。
2.10 sub
函数
2.10.1 用途
调用 \(\text{sub}()\) 可以将类对应的值加上 \(-1\)。
2.10.2 时间复杂度
\(O(1)\)。
2.11 Modular
类运算符号
2.11.1 内容
-
Modular += Modular
:将两个Modular
类相加,返回的Modular
类对应的值为两个Modular
类对应的值的和。 -
Modular -= Modular
:将两个Modular
类相减,返回的Modular
类对应的值为两个Modular
类对应的值的差。 -
Modular *= Modular
:将两个Modular
类相乘,返回的Modular
类对应的值为两个Modular
类对应的值的积。 -
Modular /= Modular
:将两个Modular
类相除,返回的Modular
类对应的值为两个Modular
类对应的值的商。 -
Modular ^= long long
:返回对应的值为 \(\text{Modular}^{\text{long long}}\) 的Modular
类。
上面的运算符可以使用为 Modular + Modular, Modular - Modular, Modular * Modular, Modular / Modular, Modular ^ long long
。
2.11.2 时间复杂度
除求商或求幂运算的时间复杂度为 \(O(\log mod)\),其他运算均为 \(O(1)\)。
2.12 Modular
类比较符号
2.12.1 内容
-
Modular == Modular
:若第一个Modular
类对应的值与第二个Modular
类对应的值相等则返回true
,否则返回false
。 -
Modular != Modular
:若第一个Modular
类对应的值与第二个Modular
类对应的值不相等则返回true
,否则返回false
。 -
Modular > Modular
:若第一个Modular
类对应的值大于第二个Modular
类对应的值则返回true
,否则返回false
。 -
Modular < Modular
:若第一个Modular
类对应的值小于第二个Modular
类对应的值则返回true
,否则返回false
。 -
Modular >= Modular
:若第一个Modular
类对应的值大于等于第二个Modular
类对应的值则返回true
,否则返回false
。 -
Modular <= Modular
:若第一个Modular
类对应的值小于等于第二个Modular
类对应的值则返回true
,否则返回false
。
2.12.2 时间复杂度
\(O(1)\)。
2.13 addmod, submod, mulmod, divmod
函数
2.13.1 用途
这四个函数是用来将 Modular
类和 long long
进行运算的。
2.13.2 时间复杂度
addmod, submod, mulmod
函数都是 \(O(1)\) 的。divmod
函数是 \(O(\log mod)\) 的。