中国剩余定理(CRT)及其扩展(ExCRT)
中国剩余定理 CRT
推导
给定 个同余方程
两两互质
令 ,求
解决该问题的方法是构造。
我们假定最终答案的形式是一个 个项的和式,对每个同余方程的构造反应在对应项的系数上。
如果要对每一个项分别构造,就要求为每一项乘上一个合适的数,使得每项构造的系数对其他方程的结果没有影响。
容易想到构造
显然该数仅在模 时不为 ,于是改变该项的系数将不会对其他方程造成影响。
现在我们希望该项模 意义下是 ,但上一次的构造残留下了一个 。简单粗暴的乘上 在模 意义下的逆元 ,让该项在模 意义下变为 ,然后乘上 就构造出来了。
综上,答案为
模数互质条件保证了 非 ,进而保证了 的存在。
实现
大部分题的 都是质数,求逆元快速幂即可。
对于一般的情况,上ExGCD就行。
板题:洛谷P1495 曹冲养猪
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<ctime> #include<cstdlib> #include<algorithm> #include<queue> #include<vector> #include<map> #include<set> using namespace std; typedef long long ll; namespace ExGcd{ ll x,y; ll ExGcd(ll a,ll b){ ll ans; if(a%b==0){ x=0;y=1;ans=b; }else{ ans=ExGcd(b,a%b); ll x1=x,y1=y; x=y1;y=x1-a/b*y1; } return ans; } bool SolveEqu(ll a,ll b,ll c){ ll d=ExGcd(a,b); if(c%d!=0) return 0; x*=c/d;y*=c/d; x=(x%b+b)%b; y=(c-a*x)/b; return 1; } } ll Inv(ll a,ll m){ ExGcd::SolveEqu(a,m,1); return ExGcd::x; } const ll CRTN=20; namespace CRT{ ll N; ll m[CRTN],a[CRTN]; ll Sol(){ ll ans=0,M=1; for(ll i=1;i<=N;i++) M*=m[i]; for(ll i=1;i<=N;i++){ ll Mi=M/m[i]; ans=(ans+Mi*Inv(Mi,m[i])*a[i])%M; } return ans; } } int main(){ using namespace CRT; scanf("%lld",&N); for(ll i=1;i<=N;i++) scanf("%lld%lld",&m[i],&a[i]); printf("%lld",Sol()); return 0; }
扩展中国剩余定理 ExCRT
ExCRT和CRT并没有什么关系,正如ExLucas和Lucas也没什么关系
其实从纯推理的角度来看,ExCRT可能还要好想一点(
推导
问题同CRT,但是模数是任意的,并不要求互质。
这时,我们就不能保证存在逆元了。那么如何解决该问题呢?
考虑如何合并两个方程。如果我们找到了合并的方法,就能如法炮制将个方程依次合并起来,得到答案。
去掉同余,化为不定方程
于是得到
只要找到一组满足该式的 和 ,就能反算出 ,实现合并。
而我们得到的是一个二元一次不定方程,可以用ExGCD求解。
化为标准式
解就是了。由裴蜀定理,若 不成立,说明同余方程组无解。
于是最后化得的合并式为
Update 2020/12/02 关于合并后的模数
之前没有讲清楚 是怎么来的,这里补充一笔。
根据二元一次不定方程理论, 的通解形式应为 ( 是某一个特解),此时带回得到 ,模上个 就是最终的合并式了。
实现
唯一需要注意的地方是,本来解方程应该解 ,但ExGCD不好处理负数,所以把 改成了 。因为我们并不需要用到 ,所以不会影响求解。
板题:poj2891 Strange Way to Express Integers or 洛谷P4777 扩展中国剩余定理(EXCRT)
会被卡乘法爆ll,懒得改
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<ctime> #include<cstdlib> #include<algorithm> #include<queue> #include<vector> #include<map> #include<set> using namespace std; typedef long long ll; namespace ExGcd{ ll x,y; ll ExGcd(ll a,ll b){ ll ans; if(a%b==0){ x=0;y=1;ans=b; }else{ ans=ExGcd(b,a%b); ll x1=x,y1=y; x=y1;y=x1-a/b*y1; } return ans; } bool SolveEqu(ll a,ll b,ll c){ ll d=ExGcd(a,b); if(c%d!=0) return 0; x*=c/d;y*=c/d; x=(x%b+b)%b; y=(c-a*x)/b; return 1; } } ll Gcd(ll a,ll b){ if(a%b==0) return b; return Gcd(b,a%b); } namespace ExCRT{ ll a1,m1; void Init(){ a1=0;m1=1; } void Expand(ll a2,ll m2){ ExGcd::SolveEqu(m1,m2,a2-a1); ll y1=ExGcd::x; ll mn=m1*m2/Gcd(m1,m2); a1=(m1*y1+a1)%mn; m1=mn; } } int main(){ ll N;scanf("%lld",&N); ExCRT::Init(); for(ll i=1;i<=N;i++){ ll a,m;scanf("%lld%lld",&m,&a); ExCRT::Expand(a,m); } printf("%lld",ExCRT::a1); return 0; }
2019/12/21
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现