中国剩余定理(CRT)

姓名: 石润禾
赛道: 提高组
类型: 算法技能

中国剩余定理(CRT)

关键词: 数论、中国剩余定理、CRT
前置知识:逆元,同余,扩展欧几里得定理

  • 引入:
    如何找到一个最小的数,使得这个数除以3余2,除以5余3,除以7余2?
    使用CRT的解法如下:

    1.找到三个数:从3和5的公倍数中找出被7除余1的最小数15,从3和7的公倍数中找出被5除余1的最 小数21,最后从5和7的公倍数中找出除3余1的最小数70。
    2.将15乘2(被7除的余数)=30,21乘3(被5除的余数)=63,70乘2(被3除的余数)=140,
    3.把上面的3个数相加,即30+63+140=233,让233对3,5,7的最小公倍数105取模,得到23,23即为答案

  • 定义:
    中国剩余定理可以求解以下形式的一元线性同余方程组:

    \begin{cases} x ≡ a_1\,(mod\;n_1)\newline x ≡ a_2\,(mod\;n_2)\newline ...\newline x ≡ a_k\,(mod\;n_k) \end{cases}

    具体过程:
    1.计算所有模数的积 n
    2.对于第 i 个方程,
    (1) 计算 m_i=\frac{n}{n_i}
    (2) 计算 m_i 在模 n 意义下的逆元( m_i^{-1}(mod\;n)
    (3) 计算 c_i=m_i·m_i^{-1}
    3.此方程组在 mod\:n 意义下的唯一解为 \sum\limits^k_{i=1}a_ic_i(mod\:n)

  • 证明:
    (证明 x 满足对于任意 i=1,2,...k ,都有 x≡a_i(mod\;n_i) )
    i≠j 时,一定有 m_j≡0(mod\;n_i) ,所以 c_j≡m_j≡0(mod\;n_i)
    又有 c_i≡m_i·(m_i^{-1}\;mod\;n_i)≡1(mod\;n_i)
    所以我们得出:
    \;\;\;\sum\limits^k_{j=1}a_jc_j\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;(mod\;n_i)
    ≡a_ic_i\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;(mod\;n_i)
    ≡a_i·m_i·m_i^{-1}\;mod\;n_i\;\;\;(mod\;n_i)
    ≡a_i\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;(mod\;n_i)
    即:对于任意的 i=1,2,...k ,上面算法得出的 x 都满足 x≡a_i(mod\;n_i) ,证明上面算法正确。

  • 实现:

    long long crt(int k) 
    {
       long long n=1,ans=0;
       for (int i=1;i<=k;i++) n=n*r[i];//计算所有模数的积
       for (int i=1;i<=k;i++) 
       {
           long long m=n/r[i],b,y;
           exgcd(m,r[i],b,y);//b*m mod r[i]=1(求逆元)
           ans=(ans+a[i]*m*b%n)%n;
       }
       return (ans%n+n)%n;
    }
    
下面给一道例题吧~

题面

我们设两只青蛙跳了t次后碰面,
则此时青蛙A的坐标为x+mt,青蛙B的坐标为y+nt,由题意得
x+mt-(y+nt)=Lp(p为整数)
附上本人的代码:

#include<bits/stdc++.h> 
using namespace std;
long long xx,yy,g;
long long n,m,x,y,l;
void exgcd(long long a,long long b)
{
	if(!b)
	{
		xx=1;
		g=a;
		return;
	}
	exgcd(b,a%b);
	long long num=xx;
	xx=yy;
	yy=num-a/b*yy;
}
int main()
{
	while(cin >> x >> y >> m >> n >> l)
	{
		long long num1=n-m,num2=x-y;
		if(num1<0) num1=-num1,num2=-num2;
		exgcd(num1,l);
		if(num2%g!=0) cout << "Impossible\n";
		else
		{
			long long tmp=l/g;
			long long ans=(num2/g*xx%tmp+tmp)%tmp;
			cout << ans << "\n";
		}
	}
	return 0;
}

好啦,我的讲解到此结束了,本人是蒟蒻,若有不足请多多指教哦! :melting_face: 点个赞呗~ :pleading_face:

2 个赞

这不是提高组知识吗?

2 个赞

2 个赞