【Coel.学习笔记】拓展欧几里得与中国剩余定理
题前碎语
月考结束了,可惜状态一点都不好,要挨骂了\(qwq\)……
最后发现自己实在是不会高级的字符串算法,所以跑来做数论了!
\(PS\):数论是一个比较大的模块,所以今后会加上各种小标签。
笔记内容
包括拓展欧几里得和中国剩余定理。
拓展欧几里得
拓展欧几里得(\(Extended\) \(Euclidean\) \(Algorithm\),以下简称\(Exgcd\))是辗转相除法的拓展,能够在求得两数的最大公约数(\(Greatest\) \(Common\) \(Divisor\),以下简称\(gcd\))的同时求出二元不定方程\(ax+by=gcd(a,b)\)的一个整数解。在\(OI\)中,拓展欧几里得通常用来求解逆元,其适用范围大于只能以质数为模数的费马小定理。
关于能够使用\(Exgcd\)求解逆元的简单证明:
逆元的定义:对于\(ax\equiv 1(mod\ p)\),\(x\)为\(a\)关于\(p\)的乘法逆元。
对式子进行变形可得\(ax+kp=1\),故可用\(Exgcd\)求出一个\(x\)值。
实现过程
与辗转相除法的思路相同,假设已经计算出了下一个状态:\(b\)和\((a \% b)\)的最大公约数,并且求出了一组 \(x_1, y_1\)使得\(bx_1+(a \% b)y_1 = gcd(a,b)\)。
接下来利用这个式子进行转移(证明过程不再赘述):
当\(b=0\)时即为边界,\(x=1,y=0\),递归求解即可。
inline int Extend_Euclid(int a,int b,int &x,int &y)
{
if(b==0)
{
x=1,y=0;
return a;
}
int r=Extend_Euclid(b,a%b,x,y),temp=x;//x会被覆盖掉,所以加一个temp
x=y,y=temp-a/b*y;
return r;
}
中国剩余定理
中国剩余定理(\(Chinese\) \(Remainder\) \(Theorem\),以下简称\(CRT\))又名孙子定理,最早出现在南北朝时期的著作《孙子算经》(也就是鸡兔同笼的同出处)中。
\(PS\):这位孙子不是那位军事家,后者是春秋时期的人。
\(1801\)年,德国数学家高斯在《算术探究》中独立地写出了这条定理,后被指出与孙子的定理相同,故被称为中国剩余定理。
限于篇幅,《孙子算经》的原文此处不再给出(其实就是我懒)。
\(CRT\)用于求解形似下式的同余方程组:
其中\(m\)两两互质。
实现过程
令\(M=\prod_{i=1}^nm_i\),\(M_i=\frac{M}{m_i}\),\(t_i=M_i^{-1}\)(即\(t_i\)为乘法逆元),
则方程组的通解为
如果要求最小非负整数解,则对\(M\)取模,并让答案保持为非负数(为负数则加回到\(M\)即可)。
代码如下洛谷传送门
#include<cstdio>
#include<cctype>
#define maxn 15
long long n,a[maxn],m[maxn];
inline long long read()//快读
{
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch))
{
if(ch=='-')f=-1;
ch=getchar();
}
while(isdigit(ch))
{
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
inline long long Extend_Euclid(long long a,long long b,long long &x,long long &y)//拓展欧几里得求逆元
{
if(b==0)
{
x=1,y=0;
return a;
}
long long r=Extend_Euclid(b,a%b,x,y);
long long temp=x;
x=y;
y=temp-a/b*y;
return r;
}
inline long long Chinese_Remainder_Theorem()//算法主体
{
long long M=1,ans=0;
for(register int i=1;i<=n;i++)
M*=m[i];
for(register int i=1;i<=n;i++)
{
long long x,y,Mi=M/m[i];
Extend_Euclid(Mi,m[i],x,y);
ans=(ans+Mi*x*a[i])%M;
}
return ans<0?ans+M:ans;
}
int main()
{
n=read();
for(register int i=1;i<=n;i++)
m[i]=read(),a[i]=read();//注意题意中对应关系,第一个数是模数,第二个是余数
printf("%lld",Chinese_Remainder_Theorem());
return 0;
}
题后闲话
打公式真的累死人……\(LaTeX\)是很好用,但是公式实在太多了好难记啊……
过几天学一下拓展\(CRT\),据说题解里有学校里一位前大佬\(niiick\)的题解,很期待\(qwq\)