模运算
当我们在算法竞赛中看一些 \(\text{counting}\) 相关的题目时, 我们经常能看到:
- 答案对 \(10^9 + 7\) 取模
- 方案数对 \(998244353\) 取模
在我们刚接触算法竞赛的时候,我们总是会想「这个好办,直接对着答案 \(\% mod\) 不就行了吗。」
虽然从结果上来看,似乎确实是这样,但是其涉及到的模运算是算法竞赛非常重要的一部分, 不仅是在我们做数数题的时候取个模,还有很多基于模运算拓展出来的东西。
模与同余
给定一个 \(m\),我们说 \(a\) 和 \(b\) 在模 \(m\) 的意义下「同余」,记作:
定义:
\(a \equiv b \pmod m \Leftrightarrow m \mid (a-b)\)
也就是说:\(a\) 和 \(b\) 的差是 \(m\) 的倍数。 还有一种常见的等价写法:\(a = b + km,\ k \in \mathbb{Z}\)
举几个例子:
- \(17 \equiv 5 \pmod{12}\),因为 \(17-5=12\)
- \(-1 \equiv 10 \pmod{11}\),因为 \(-1-10=-11\)
直觉理解:
我们把整数的数轴按照 \(m\) 的步长进行折叠, 每隔 \(m\) 个数字就会重合在一起, 重合在一起的整数就是一个「同余系」。
也就是说在同余系中,我们不关心具体的数字,我们只关心模 \(m\) 的余数。
同余的关系满足三个性质:
- 自反性:\(a \equiv a \pmod m\)
- 对称性:\(a \equiv b \pmod m \Rightarrow b \equiv a \pmod m\)
- 传递性:\(a \equiv b, b \equiv c \pmod m \Rightarrow a \equiv c \pmod m\)
所以我们可以把整数按这个关系分成若干类: 每一类对应一个余数 \(0,1,\dots,m-1\)。
这 \(m\) 个类组成的集合记作 \(\mathbb{Z}/m\mathbb{Z}\),你可以理解为“模 \(m\) 的整数集合”。
模意义下的运算
假设存在:
那么:
- 加法:\(a + c \equiv b + d \pmod m\)
- 减法:\(a - c \equiv b - d \pmod m\)
- 乘法:\(ac \equiv bd \pmod m\)
这些的证明还是比较平凡的,例如对于加法:
这也就是为什么代码里我们会写:
就是每一次运算之后,我们都加一个 \(\% mod\) 就可以了。 不过要注意减法产生负数的问题,我们接下来就会提到。负数取模
数学上我们通常约定:余数在 \([0, m-1]\) 范围内。 但在 \(\text{C++}\) 中:
也就是说,当我们在 \(\text{C++}\) 中,我们进行取模的运算时, 余数会保持和左边的数字同号,左边的数字是正的,答案就是正的;是负的答案就是负的。
这就是为什么我们经常会在部分人的代码中看到:
当然也有不开函数的「古法取模」:日志
本页面创建于 2025/12/09 15:24