专题 CRT 定理 & EXCRT 定理的概述
前言
本文尽量以规范严谨的数学语言叙述有关本题的知识点,但难面水平有限。如有表述不当或逻辑漏洞者,还望不吝赐教。
算法分析
首先,读者需初步了解 CRT,知道 CRT 的理论并学会编码。由于以下与此处关系不是很大,笔者在这里不再赘述。
EXCRT,是 CRT 的特殊情况。参考 CRT 解决问题的范畴,是求解一个一次同余线性方程组。这里是存在约束的:必须要求,模数是两两互质的。参考 CRT 的推导,是采用构造法给出解的一般形式的。但是,EXCRT 的模数不保证两两互质。所以我们需要一种别样的方式给出解。
问题陈述
设同余方程组:$$ \begin{cases} x \equiv b_1 \ (\text{mod} \ a_1) \
x \equiv b_2 \ (\text{mod} \ a_2) \ \vdots \ x \equiv b_n \ (\text{mod} \ a_n) \end{cases} $$ 其中模数 \(a_1,a_2,...,a_n \in \mathbb{Z}^+\) 不一定互质,求最小正整数解 \(x\)。
数学推导
1. 基础引理
引理(线性同余方程解的存在性):
方程 \(m_0 t \equiv c \ (\text{mod} \ a_k)\) 有解当且仅当 \(\gcd(m_0,a_k) \mid c\)。
证明:由 Bézout 定理,存在整数 \(u,v\) 使得:
$ u m_0 + v a_k = d \quad (d = \gcd(m_0,a_k)) $$ 若 \(d \mid c\),则特解 \(t_0 = u \cdot (c/d)\)。
2. 归纳合并步骤
假设:前 \(k-1\) 个方程已合并为:$$ x \equiv x_0 \ (\text{mod} \ m_0) $$。
合并第 \(k\) 个方程:
-
设解形式为 \(x = x_0 + t m_0\),代入得:$$ m_0 t \equiv b_k - x_0 \ (\text{mod} \ a_k) $$。
-
记 \(d = \gcd(m_0,a_k)\),方程可解的条件为:$$ d \mid (b_k - x_0) $$。
-
方程两边除以 \(d\) 后:$$ \frac{m_0}{d} t \equiv \frac{b_k - x_0}{d} \ \left(\text{mod} \ \frac{a_k}{d}\right) $$。
-
因 \(\gcd\left(\frac{m_0}{d}, \frac{a_k}{d}\right) = 1\),存在逆元 \((\frac{m_0}{d})^{-1}\),解得:$$ t \equiv t_0 \ \left(\text{mod} \ \frac{a_k}{d}\right), \quad t_0 = \frac{b_k - x_0}{d} \cdot \left(\frac{m_0}{d}\right)^{-1} $$。
-
代回 \(x\) 表达式得通解:$$ x = x_0 + \left(t_0 + k \cdot \frac{a_k}{d}\right) m_0 \quad (k \in \mathbb{Z}) $$。
-
合并后新解为:$$ x \equiv x_0 + t_0 m_0 \ \left(\text{mod} \ \text{lcm}(m_0,a_k)\right) $$ 其中模数更新为 \(\text{lcm}(m_0,a_k) = m_0 a_k / d\)。
3. 数学归纳法
基例:\(k=1\) 时,初始解 \(x \equiv b_1 \ (\text{mod} \ a_1)\) 成立。
归纳:若前 \(k-1\) 个方程有解 \(x \equiv x_0 \ (\text{mod} \ m_0)\),则第 \(k\) 步推导保证解的存在性与更新规则。
结论:最终解为 \(x \equiv x_n \ (\text{mod} \ m_n)\)。
算法正确性验证
-
解的存在性:依赖线性同余方程的可解条件(引理)。
-
解的完备性:通过归纳法覆盖所有方程。
-
解的最小性:模数始终取最小公倍数保证解的最小正整数性。
所以按照以上的迭代更新,我们成功构造出了一个可行的方案求出解。下面是参考程序。
参考程序
#include<iostream>
#define int __int128
#define rei register int
using namespace std;
const int N=1e5+5;
int n;
int a[N],b[N];
inline int read()
{
int f=1,x=0;
char c=getchar();
while(c<'0' || c>'9') { if(c=='-') f=-1; c=getchar(); }
while(c>='0'&&c<='9') { x=(x<<1)+(x<<3)+(c^48); c=getchar(); }
return f*x;
}
void write(int x)
{
if(x<0) x=-x,putchar('-');
if(x>9) write(x/10);
putchar(x%10+'0');
}
int exgcd(int a,int b,int &x,int &y)
{
int gcd;
if(!b) { x=1,y=0; return a; }
gcd=exgcd(b,a%b,y,x);
y-=a/b*x;
return gcd;
}
int excrt(int *m,int *r)
{
int m1,m2,r1,r2,p,q;
m1=m[1],r1=r[1];
for(rei i=2;i<=n;i++)
{
m2=m[i],r2=r[i];
int gcd=exgcd(m1,m2,p,q);
if((r2-r1)%gcd) return -1;
p=p*(r2-r1)/gcd;
p=(p%(m2/gcd)+(m2/gcd))%(m2/gcd);
r1=m1*p+r1;
m1=m1*m2/gcd;
}
return (r1%m1+m1)%m1;
}
signed main()
{
n=read();
for(int i=1;i<=n;i++) a[i]=read(),b[i]=read();
write(excrt(a,b));
return 0;
}
细节
为防止数据溢出,需要开足够大的类型。