专题 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;
}
 

细节

为防止数据溢出,需要开足够大的类型。

posted @ 2025-08-07 09:02  枯骨崖烟  阅读(11)  评论(0)    收藏  举报