Resources
AryanshS

通过大量数学竞赛级别的例题和习题介绍模运算

IUSACO

内容非常简洁,本模块以此为基础

David Altizio

包含大量数学竞赛例题

CPH
PAPS1
CF

一些练习题

MONT

简介

模运算中,我们并不直接处理整数本身,而是处理它们除以 mm 后的余数, 这一过程称为“对 mm 取模”。例如,当 m=23m = 23 时,我们不直接使用 x=247x = 247,而使用 xmod23=17x \bmod 23 = 17。通常,题目会给出一个很大的质数 mm;最常见的两个模数是 109+710^9 + 7998244353=119223+1998\,244\,353=119\cdot 2^{23}+1。模运算可以避免数值超出内置数据类型 所能表示的范围,因为我们可以按照下列公式随时取余:

(a+b)modm=(amodm+bmodm)modm(a+b) \bmod m = (a \bmod m + b \bmod m) \bmod m
(ab)modm=(amodmbmodm)modm(a-b) \bmod m = (a \bmod m - b \bmod m) \bmod m
(ab)(modm)=((amodm)(bmodm))modm(a \cdot b) \pmod{m} = ((a \bmod m) \cdot (b \bmod m)) \bmod m
abmodm=(amodm)bmodma^b \bmod {m} = (a \bmod m)^b \bmod m

模幂

Focus Problem – try your best to solve this problem before continuing!

资料

Resources
cp-algo

二进制快速幂可以高效计算 xnmodmx ^ n \mod m。其思想是把指数 nn 按二进制 拆分。例如,510=510102=58525 ^ {10}=5 ^ {1010_2}=5 ^ 8 \cdot 5 ^ 2。因此,只要我们 知道所有指数为二的幂时的 xyx ^ y(即 x1x ^ 1x2x ^ 2x4x ^ 4\dotsx2log2nx ^ {2^{\lfloor{\log_2n} \rfloor}}),就能在 O(logn)\mathcal{O}(\log n) 时间内计算 xnx ^ n

为了处理模数 mm,注意取模与乘法相容。因此可以直接实现上述二进制快速幂, 并在每次乘法后将结果对 mm 取模。

题解——Exponentiation

可以使用 Python 内置的 pow 函数高效计算模幂。

MOD = 10**9 + 7
for _ in range(int(input())):
first, second = [int(i) for i in input().split()]
print(pow(first, second, MOD))

模逆元

模逆元对应于实数运算中的倒数:要在模意义下用 aa 除以 bb,可以将 aa 乘以 bb 的模逆元。这里我们只考虑模数 pp 为质数的情况。

例如,22 在模 p=109+7p=10^9+7 下的逆元为 i=p+12=5108+4i=\frac{p+1}{2}=5\cdot 10^8+4。这意味着对任意整数 xx,都有

(2x)ix(2i)x(mod109+7).(2x)\cdot i\equiv x\cdot (2i)\equiv x\pmod{10^9+7}.

例如,10i5(mod109+7)10i\equiv 5\pmod{10^9+7}

Resources
cp-algo

介绍了多种求模逆元的方法;这里只讨论其中的第二种

使用快速幂

费马小定理 (不要与费马定理混淆)指出:对于所有不能被 pp 整除的整数 aa, 都有 ap11(modp)a^{p - 1} \equiv 1 \pmod{p}。于是 ap2a1(modp)a^{p-2} \cdot a \equiv 1 \pmod{p},因此 ap2a^{p - 2} 就是 aa 在模 pp 意义下的一个逆元。

MOD = 10**9 + 7
x = pow(2, MOD - 2, MOD)
print(x) # 500000004
assert 2 * x % MOD == 1

使用欧几里得除法

我们还可以通过欧几里得除法求模逆元。给定质数模数 m>am > a,有:

m=ka+rm = k \cdot a + r

其中 k=mak = \lfloor \frac{m}{a} \rfloorr=mmodar = m \mod a。于是:

0=ka+rmodm    r=kamodm    ra1=kmodm    a1=kr1modm\begin{align*} & 0 = k \cdot a + r \mod m \\ \iff{} & r = -k \cdot a \mod m \\ \iff{} & r \cdot a^{-1} = -k \mod m \\ \iff{} & a^{-1} = -k \cdot r^{-1} \mod m \end{align*}

下面是上述公式的一份简短递归实现:

def inv(x: int) -> int:
return x if x <= 1 else MOD - MOD // x * inv(MOD % x) % MOD

这种方法的优点是,可以在 O(MOD)\mathcal{O}(MOD) 时间内预处理区间 [1,MOD)[1, MOD) 中所有数的模逆元。

inv[1] = 1 # assume we already defined this array
for i in range(2, MOD):
inv[i] = MOD - MOD / i * inv[MOD % i] % MOD

由于计算模 pp 的逆元需要 O(logp)\mathcal{O}(\log p) 时间,在循环中频繁进行除法 会显著增加程序运行时间。如果同一个(或同一批)数的逆元会被多次使用,最好 提前将其预处理出来。

此外,必须始终确保不会除以 00。需要注意的是,一个非零数取模后也可能变成 00,因此除数不是常量时尤其要小心。

Optional: 另一种计算模逆元的方法

还可以使用扩展欧几里得算法。参见进阶章节中的对应模块。

C++

模板

下面这些模板实现了模整数类型:数值达到给定模数后会自动取模。

Resources
Benq
Benq

可以在比赛中现场写出的简短版本

AtCoder

包含一个 modint.hpp 文件

AtCoder

下面是使用 Benq 模板的示例:

#include <bits/stdc++.h>
using namespace std;
const int MOD = 1e9 + 7;
Code Snippet: ModInt (Click to expand)
int main() {
{
int a = 1e8, b = 1e8, c = 1e8;

下面是使用 AtCoder 模板的示例:

#include <bits/stdc++.h>
using namespace std;
// https://atcoder.github.io/ac-library/production/document_en/modint.html
// (included in atcoder grading)
#include <atcoder/modint>
using mint = atcoder::modint;
const int MOD = 1e9 + 7;

Java

Python

练习题

StatusSourceProblem NameDifficultyTags
CSESEasy
Show TagsModular Arithmetic
CFEasy
Show TagsModular Arithmetic
KilonovaMedium
Show TagsMath, Prime Factorization
YSMedium
Show TagsModular Arithmetic
CSESMedium
Show TagsModular Arithmetic
GoldHard
Show TagsBinary Search, Modular Arithmetic

Module Progress: