中国剩余定理学习笔记
中国剩余定理
作用
中国剩余定理 (Chinese Remainder Theorem, CRT),也称孙子定理,是用来求解线性同余方程组,即如下面的方程组:
这个定理可以在 $O(n \log p) $ 的时间内求解这个方程组的最小整数解,要求 \(p_1,p_2,\dots p_n\) 两两互质。
定理
这个定理告诉我们要怎么求解 \(x\) 的最小正整数值。
实现步骤:
-
设 \(M = \displaystyle\prod_{1 \le i \le n} p_i\), 求出 \(M\) 。
-
设 \(m_i = \frac{M}{p_i}\) , \(t_i = m_i^{-1} \pmod {p_i}\) ,求出每个 \(m_i\) 。
-
\(x\) 的一个解是 \(\displaystyle\sum_{1\le i\le n}a_i\times m_i \times t_i\) ,最小的正整数解就是 \(x \bmod M\) 。
证明非常简单,对于任意一个方程 \(x \equiv a_k \pmod {p_k}\) ,除了 \(k\) 以外的所有 \(a_i \times m_i \times m_i^{-1}\) 整除 \(p_k\) ,而 \(a_k \times m_k \times m_k^{-1}\) 在模 \(p_k\) 意义下变为:
得证。
总的时间复杂度是 \(O(n \log p)\) 。
code
int n;
typedef long long ll;
ll p[15] = {0}, a[15] = {0};
ll phi(ll x) {
ll ans = x;
for (ll d = 2; x > 1; d++) {
if (d * d > x)
return ans / x * (x - 1);
if (x % d == 0) {
ans = ans / d * (d - 1);
while (x % d == 0)
x /= d;
}
}
return ans;
}
ll fpow(ll a, ll b, ll p) {
if (b == 0)
return 1ll;
ll ans = fpow(a, b / 2, p);
ans = ans * ans % p;
if (b % 2 == 1)
ans = ans * a % p;
return ans;
}
ll CRT() {
ll mul = 1;
for (int i = 1; i <= n; i++)
mul *= p[i];
ll ans = 0;
for (int i = 1; i <= n; i++)
ans += a[i] * fpow(mul / p[i], phi(p[i]) - 1, p[i]) % mul * (mul / p[i]) % mul, ans %= mul;
return ans % mul;
}
扩展中国剩余定理(exCRT)
考虑 CRT 的求解过程,关键的是求逆元,但是如果模数两两不互质就不存在逆元。所以 CRT 要求模数两两互质,而 exCRT 就可以处理两两不互质的情况。
我们先考虑两个方程的情况:
显然,如果这个方程有解,那么一定存在两个整数 \(p, q\) 使得 \(x = pn + a = qm + b\)。
改变一下形式得到:\(pn - qm=b - a\)。
这样就变成了二元一次方程的求解,根据裴蜀定理,如果 \(\gcd(n,m) \not | b - a\),则方程无解。
否则,我们可以用 exgcd 找到一组解 \((p_0, q_0)\),我们就可以得到原方程的通解:
将通解代入原来的式子就能得到:
将 \(np_0+a\) 记作 \(c\), 我们可以将 \(x\) 表示为一个同余方程的解,也就是:
这样我们就得到了一个新的同余方程。
如果还有第三个方程,就用这个方程与其合并,和上面一样,最后将所有方程都合并后就能得到答案。
一些题目
题目1: 有 \(n\) 个人站成一圈,顺时针按照 \(1 \sim k\) 报数,每次报到 \(k\) 的人出局。已知出去顺序,求最小的 \(k\),\(n \le 20\)。(P5944 [POI2002] 出圈游戏)
思路:
我们考虑第一个出局的人是 \(x\),则说明 \(k \equiv x \pmod n\)。
以此类推我们可以得到 \(n\) 个方程:
然后用 exCRT 求解即可。
题目2: 给定 \(n,G (n,G \le 10^9)\),求:
思路:
经典题目。
首先,由于 \(999911659\) 是个质数,根据费马小定理,\(G^b \equiv G^{b \bmod 999911658}\)。
所以我们只要求出这个值就能用快速幂求出答案。
现在考虑如何求 \(\sum_{k|n}\binom{n}{k} \bmod 999911658\)。
首先,我们可以枚举 \(k\), 复杂度 \(O(\sqrt{n})\)。
现在考虑如何求 \(\binom{n}{k} \bmod 999911658\)。
由于模数不是个质数,而且 \(n\) 太大,没法预处理逆元。
但是我们发现 \(999911658 = 2 \times 3 \times 4679 \times 35617\),刚好是几个质数的乘积。
所以我们可以对每个质数分别求出对其取模的值,然后用 CRT 将这些同余方程合并起来。
现在考虑如何求 \(\binom{n}{k} \pmod p\),(\(p\) 是质数)。
一种方法是卢卡斯定理直接求,比较简单,时间复杂度 \(O(\log n)\)。
当然还有一种方法可以不用卢卡斯定理。
众所周知,\(\binom{n}{k}=\frac{n!}{k!(n-k)!}\)。
我们首先计算出 \(n!\),\(k!\) 和 \((n-k)!\) 中分别含有 \(p\) 的几次方。
这个可以通过经典结论:\(n!\) 中 \(p\) 的次数为 \(\sum_{i=0}\lfloor\frac{n}{p^i}\rfloor\)。
然后我们看一下 \(n!\) 与 \(k!(n-k)!\) 是否次数相等,如果不相等,说明是 \(p\) 的倍数,答案就是 \(0\)。
否则,我们考虑求出这三个阶乘再去掉所有的 \(p\) 后在 \(\bmod p\) 意义下的值和逆元,就可以求出答案。
不妨设 \(f(n)\) 表示 \(n!\) 去掉所有 \(p\) 后对 \(p\) 取模的值。
当 \(n < p\) 是,\(f(n)=n! \bmod p\)。
否则,\(f(n)=f(\lfloor \frac{n}{p}\rfloor) \times (-1)^{\lfloor \frac{n}{p}\rfloor} \times (n \bmod p)! \pmod p\)。
感性理解一下,我们将 \(1 \sim n\) 分成若干段,每一段都是 \([p+1 , (k+1)p]\)。
首先,末尾会有一些剩下,在模 \(p\) 意义下就是 \((n \bmod p)!\),所以要乘上这个东西。
再看每一段,去掉每段最后那个 \(p\) 的倍数,那么在模 \(p\) 意义下乘起来就是 \((p-1)!\),根据威尔逊定理,\((p-1)! \equiv 1 \pmod p\),所以我们需要乘上 \((-1)^{\lfloor \frac{n}{p}\rfloor}\)。
再看 \(p\) 的倍数,将 \(p\) 去掉后就是 \(1,2,\dots,\lfloor \frac{n}{p}\rfloor\),于是这就成了一个子问题,递归求解即可。
时间复杂度是 \(O(\log n)\) 的,和卢卡斯定理一样。
然后就能做出这道题了。