https://www.luogu.com.cn/problem/P1495
中国剩余定理(CRT)是个什么东西呢?
\[\left\{ \begin{array}{c} x\equiv a_1(mod\;\;m_1)\\ x\equiv a_2(mod\;\;m_2)\\ \cdots\\ x\equiv a_n(mod\;\;m_n) \end{array} \right. \]
其中:\(m_1,m_2,……,m_n\)两两互质
让我们求一个最小的非负整数\(x\),使得这个方程组成立
神葱曰:
大数翻倍法
具体步骤:
将这\(n\)个方程按\(m_i\)从大到小排序,使得:
\[m_1\geq m_2 \geq ……m_n \]
假设我们现在只有两个方程组:
我们令\(x=k×m_1+a_1\),找到一个最小的\(k\),使得:
\(k×m_1+a_1=a_2(mod\;\;m_2)\)
这样\(x\)就满足了两个方程
但\(k\)枚举的上限是多少呢?
\[∵0×m_1+a_1=m_2×m_1+a_1(mod\;\;m_2) \]
所以这个东西是有循环节的
答案一定是在\(=>[0,m_2]\)这段区间内的
即\(k\)最多枚举\(m_2\)次
否则,我们就可以说它无解
因此我们做\(n-1\)次这样的操作,即可把\(n\)个方程组解出
时间复杂度:\(O(\sum_{i=1}^n m_i -max(m_1,\cdots,m_n))\)
但这个算法什么时候会T呢?
我们发现一个神奇的事情:\(n\)越大,这个算法越快
因为出题人必须满足一个条件:
\[\prod_{i=1}^n m_i \leq long\;long_{max} \]
所以当\(n\)较大时,\(\sum_{i=1}^n m_i\)会较小。
只有当\(n=2,m_1,m_2\leq10^9\)时才有可能会T
代码:
#include
#include
#include
#include
using namespace std;
const int N=20;
#define LL long long
int n;
struct node
{
LL m,r;//模数与余数
}p[N];
LL res,add;
bool cmp(node A,node B)
{
return A.m>B.m;//按mi排序
}
LL gcd(LL A,LL B)
{
if(B==0)return A;
return gcd(B,A%B);
}
LL lcm(LL A,LL B)
{
return A*B/gcd(A,B);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%lld%lld",&p[i].m,&p[i].r);
res=p[1].r;add=p[1].m;
for(int i=2;i<=n;i++)
{
while(res%p[i].m!=p[i].r)
{
//只要不等于余数,就不停地加
(枚举k)
res=res+add;
}
add=lcm(add,p[i].m);
//注意:此时加数也要累积
}
printf("%lld",res);
return 0;
}
思考有没有什么其它的做法?
定理:若\(m_1,m_2,……,m_n\)是两两互质的正整数
\(C=\prod_{i=1}^n m_i,M_i =\frac{C}{m_i},t_i×M_i=1(mod\;\;m_i)\)
方程组的解为:\(\sum_{i=1}^n a_i M_i t_i\)
且在\(mod\;\;C\)意义下有唯一正整数解
自己代入一下即可
如何算\(t_i\)?
其实\(t_i\)就是\(M_i\)在\(mod\;\;m_i\)意义下的逆元
费马小定理?但前提是\(m_i\)是质数,题目中没有保证
对于这道题可以用\(exgcd\)
代码:
#include
#include
#include
#include
using namespace std;
const int N=20;
#define LL long long
int n;
LL m[N],a[N],C=1,M[N],t[N];
LL exgcd(LL a,LL b,LL &x,LL &y)//扩展欧几里得
{
if(b==0)
{
x=1;y=0;
return a;
}
LL d=exgcd(b,a%b,y,x);
y-=a/b*x;
return d;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>m[i]>>a[i];
C*=m[i];
}
for(int i=1;i<=n;i++)
{
M[i]=C/m[i];
LL x,y;
exgcd(M[i],m[i],x,y);//exgcd求逆元
t[i]=x;
}
LL res=0;
for(int i=1;i<=n;i++)
{
res=res+(a[i]*M[i]*t[i]);
}
cout<<(res%C+C)%C<
EXCRT(扩展中国剩余定理)
这是个什么东西呢?
唯一比CRT多的一点就是它不能保证\(m_i\)两两互质
在这里,我们就不能直接构造出一个解了,因为\(M_i\)与\(m_i\)不一定互质,因此\(M_i\)不一定有逆元
如何做呢?
假设我们现在已经求出了前k-1个方程的一个解x
而:\(lcm(m_1,m_2,……,m_{k-1})=M\)
则我们现在要找出一个最小的正整数\(T\),使得:
\[x+T×M=a_k\;(mod\;\;m_k) \]
推导一下,即:
\[x+T×M+y×m_k=a_k \]
\[T×M+y×m_k=a_k-x \]
仔细观察即可发现,这是exgcd的一般形式:
\[ax+by=gcd(a,b) \]
这里\(M=a,m_k=b,a_k-x=g×p\),我们直接用exgcd求解即可
根据裴蜀定理:\(ax+by=g\)的充要条件是\(gcd(a,b)|g\)
而题目中保证有解
所以\(p\)一定是整数,所以答案就是\(x×(g÷gcd(a,b))\)
代码:
#include
#include
#include
#include
using namespace std;
const int N=100010;
#define LL long long
LL n,m[N],r[N],res,M;
LL exgcd(LL a,LL b,LL &x,LL &y)
{
if(b==0)
{
x=1,y=0;
return a;
}
LL d=exgcd(b,a%b,y,x);
y-=a/b*x;
return d;
}//扩展欧几里得
LL mul(LL a,LL b,LL mod)
{
LL ans=0;
while(b)
{
if(b&1)ans=(ans+a)%mod;
a=(a+a)%mod;
b>>=1;
}
return ans;
}//龟速乘
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%lld%lld",&m[i],&r[i]);
res=r[1];M=m[1];//初始
for(int i=2;i<=n;i++)
{
//ax+by=g
LL a=M,b=m[i],g=((r[i]-res)%b+b)%b;
//前面推导的式子
LL x,y;
LL d=exgcd(a,b,x,y),t=b/d;
x=mul(x,g/d,b);//g÷d一定是整数(裴蜀定理)
//由于可能会乘法溢出,所以x要模上mi
res+=x*M;//答案加上我们求的解
M*=t;//计算最小公倍数
res=(res%M+M)%M;//不停地模M
}
printf("%lld",(res%M+M)%M);
return 0;
}