中国剩余定理

前言

题目链接

戳我

前置知识

  1. 乘法逆元
  2. 扩展欧几里得
  3. 一些简单的数学知识(小学奥数)

正文

引子

在《孙子算经》中有这样一个问题:“今有物不知其数,三三数之剩二(除以3余2),五五数之剩三(除以5余3),七七数之剩二(除以7余2),问物几何?”这个问题称为“孙子问题”,该问题的一般解法国际上称为“中国剩余定理”。

在《孙子歌诀》中给出了解决这个问题的解法:三人同行七十稀,五树梅花廿一支,七子团圆正半月,除百零五便得知。很是朗朗上口,但这是什么意思呢?

具体解法分三步:
找出三个数:
1.从3和5的公倍数中找出被7除余1的最小数15,从3和7的公倍数中找出被5除余1 的最小数21,最后从5和7的公倍数中找出除3余1的最小数70。
2.用15乘以2(2为最终结果除以7的余数),用21乘以3(3为最终结果除以5的余数),同理,用70乘以2(2为最终结果除以3的余数),然后把三个乘积相加(152+213+70*2)得到和233。
3.用233除以3,5,7三个数的最小公倍数105,得到余数23,即233%105=23。这个余数23就是符合条件的最小数。

说实话我不知道这是怎么想出来的,tql。

现在原问题可以化成:

\[ f(x)=\left\{ \begin{aligned} x≡a_1 \pmod{m_1} \\ x≡a_2 \pmod{m_2}\\ x≡a_3 \pmod{m_3}\\ x≡a_4 \pmod{m_4}\\ \end{aligned} \right. \]

现在假设$m_1,m_2,m_3,m_4,...,m_n相互互质,则可以进行如下构造

令:$$M=\prod_{i=1}^nm_i$$设:$$M_i=M/m_i,t_i=exgcd(M_i,m_i).x$$
则x的通解为:

\[x=KM+\sum_{i=1}^na_i*t_i*M_i \]

所以最小的x为:

\[x=\sum_{i=1}^na_i*t_i*M_i \]

然后直接算就好了

code

#include<bits/stdc++.h>
#define rg register
#define int long long
#define file(x) freopen(x".in","r",stdin);freopen(x".out","w",stdout);
using namespace std;
int read(){
    int x=0,f=1;
	char c=getchar();
    while(c<'0'||c>'9') f=(c=='-')?-1:1,c=getchar();
    while(c>='0'&&c<='9') x=x*10+c-48,c=getchar();
    return f*x;
}
void exgcd(int a,int b ,int &x,int &y){
	if(!b){x=1,y=0;return;}
	exgcd(b,a%b,x,y);
	int t=x;
	x=y,y=t-(a/b)*y;
}
int n,x,y,a[11],p[11];
void crt(){
	int ans=1,js=0;
	for(int i=1;i<=n;i++)
		ans*=p[i];
	for(int i=1;i<=n;i++){
		int t=ans/p[i];
		exgcd(t,p[i],x,y);
		js+=a[i]*t%ans*x%ans;
		js%=ans;
	}
	printf("%lld",(js+ans)%ans);
}
void init(){
	n=read();
	for(int i=1;i<=n;i++)
		p[i]=read(),a[i]=read();
}
main(){
//	file("");
	init();
	crt();
	return 0;
}
posted @ 2018-12-30 10:36  撤云  阅读(791)  评论(0编辑  收藏  举报
……