[總結] (擴充套件)中國剩餘定理
中國剩餘定理
有物不知其數,三三數之剩二,五五數之剩三,七七數之剩二。問物幾何?
解線性方程組:
\(\left(\begin{matrix} x\equiv b_1 \pmod{a_1}\\x \equiv b_2 \pmod{a_2}\\ \cdots \\ x\equiv b_n \pmod{a_n}\end{matrix} \right)\)
解法
\(int \ lcj=a_1*a_2\cdots*a_n,m_i=lcj/a[i],m_i^{-1}=m_i\)關於\(a[i]\)的逆元
\(ans=\sum_{i=1}^nb[i]*m_i*m_i^{-1}\)
如果是最小的非負整數解,\(ans=(ans+lcj)mod\ lcj\)
#include <iostream> #include <cstdio> #include <cstring> #define int long long using namespace std; const int maxn = 15; int a[maxn],b[maxn],lcj=1; void exgcd(int a,int b,int &x,int &y){ if(!b){ x=1;y=0; return ; } exgcd(b,a%b,x,y); int tmp=x;x=y; y=tmp-(a/b)*y; } int n,ans=0; signed main(){ scanf("%lld",&n); for(int i=1;i<=n;i++)scanf("%lld%lld",a+i,b+i),lcj=1LL*lcj*a[i]; for(int i=1;i<=n;i++){ int mi=lcj/a[i]; int mi_=0,y0=0; exgcd(mi,a[i],mi_,y0); (ans+=1LL*b[i]*mi*mi_)%=lcj; } printf("%lld\n",(ans+lcj)%lcj); return 0; }
擴充套件中國剩餘定理
與中國剩餘定理的區別
擴充套件中國剩餘定理用來解決模數不互質的情況。
解法
採用合併答案的思想。
- 如果我們解決了前 \(k-1\) 個同餘方程,令 \(M=\Pi_{i=1}^{k-1}a[i]\) ,那麼前 k-1 個方程的通解為 \(x=x+t*M,(t\in Z)\)。
現在考慮第 \(k\) 個方程。
\(x\equiv b_k \pmod{a_k}\)
等價於:
\(x+t* M\equiv b_k \pmod{a_k}\) 有解。
即 \(t* M \equiv b_k-x \pmod{a_k}\)
現在考慮合併答案。
\(x=x+t* M,M=lcm(M,a[k])\)。
實現
- 需要用到快 \((gui)\) 速乘,可以防止溢位
int mul(int a,int b,int P){
int res=0;
while(b){
if(b&1)res=(res+a)%P;
a=(a+a)%P;
b>>=1;
}
return res;
}
#include <iostream>
#include <cstdio>
#include <cstring>
#define int long long
using namespace std;
const int maxn = 1e5 + 10;
int n,ai[maxn],bi[maxn];
int mul(int a,int b,int P){
int res=0;
while(b){
if(b&1)res=(res+a)%P;
a=(a+a)%P;
b>>=1;
}
return res;
}
int exgcd(int a,int b,int &x,int &y){
if(!b){
x=1;y=0;return a;
}
int gcd=exgcd(b,a%b,x,y);
int tmp=x;x=y;
y=tmp-(a/b)*y;
return gcd;
}
int exCRT(){
int x,y;
int ans=bi[1],M=ai[1];
for(int i=2;i<=n;i++){
int a=M,b=ai[i],c=(bi[i]-ans%b+b)%b;
int gcd=exgcd(a,b,x,y),bg=b/gcd;
if(c%gcd!=0)return -1;
x=mul(x,c/gcd,bg);
ans+=x*M;
M*=bg;
ans=(ans%M+M)%M;
}
return (ans%M+M)%M;
}
signed main(){
scanf("%lld",&n);
for(int i=1;i<=n;i++)scanf("%lld%lld",ai+i,bi+i);
printf("%lld",exCRT());
}
例題
前言
中國剩餘定理起的是合併答案的作用。
題解
解同餘方程組:
\(\left(\begin{matrix}b_1* x\equiv a_1 \pmod{p_1}\\b_2*x \equiv a_2 \pmod{p_2}\\ \cdots \\ b_n*x\equiv a_n \pmod{p_n}\end{matrix} \right)\)
- 有係數了怎麼辦??
為了合併答案,我們需要把前面的係數 \(b_i\) 去掉
無非就是解 \(n\) 個不定方程然後合併答案,這亦然是擴充套件中國剩餘定理的根本
- 因此在處理中國剩餘定理的係數時,可以嘗試解完不定方程再合併答案。
因此問題變成了。
\(\left(\begin{matrix} x\equiv x_0 \pmod{P_1}\\x \equiv x_0 \pmod{P_2}\\ \cdots \\ x\equiv x_n \pmod{P_n}\end{matrix} \right)\)
其中 \(P\) 為每一個不定方程的剩餘系通解的模數,即 \(b/gcd\) , \(x0\) 為解得的不定方程的最小非負整數解。
細節部分
根據中國剩餘定理可知,最後的通解為:
\(x=x+k* M,k\in Z\)
- 這裡求的就不是最小非負整數解了
每攻擊一個巨龍都有一定的次數,所以最終次數一定是 \(\geq\) 最大次數的。
因此在剩餘系裡找一個合法的最小的解就 ok 了
- 這裡用 \(multiset\) 處理前後綴,千萬別用 \(set\) !!
#include <iostream>
#include <cstdio>
#include <set>
#include <cstring>
#include <algorithm>
using namespace std;
#define LL long long
const int maxn = 1e5 + 10;
LL read()
{
LL f=1,x=0;
char ss=getchar();
while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
return f*x;
}
int T;
LL n,m,a[maxn],p[maxn],atk[maxn],mi[maxn],maxx=0;
LL mod[maxn],x0[maxn];
multiset<LL> s;
LL exgcd(LL a,LL b,LL &x,LL &y){
if(!b){
x=1;y=0;
return a;
}
LL g=exgcd(b,a%b,x,y);
LL tmp=x;x=y;
y=tmp-(a/b)*y;
return g;
}
LL mul(LL a,LL b,LL P){
LL res=0;
while(b){
if(b&1)res=(res+a)%P;
a=(a+a)%P;
b>>=1LL;
}
return res;
}
LL ans=0,M=0;
void clear(){
s.clear();
ans=0;M=0;
maxx=0;
memset(atk,0,sizeof atk);
memset(mod,0,sizeof mod);
memset(x0,0,sizeof x0);
return ;
}
void init(){
clear();
n=read();m=read();
LL input;
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1;i<=n;i++)p[i]=read();
for(int i=1;i<=n;i++)mi[i]=read();
for(int i=1;i<=m;i++){
input=read();s.insert(input);
}
for(int i=1;i<=n;i++){
multiset<LL> :: iterator it=s.upper_bound(a[i]);
if(it!=s.begin())it--;
atk[i]=(*it);
s.erase(it);
s.insert(mi[i]);
}
//for(int i=1;i<=n;i++)cerr<<atk[i]<<" ";
return ;
}
LL exCRT(LL *ai,LL *bi){
ans=bi[1];
M=ai[1];
LL x,y;
for(int i=2;i<=n;i++){
LL a=M,b=ai[i],c=(bi[i]-ans%b+b)%b;
LL gcd=exgcd(a,b,x,y),bg=b/gcd;
if(c%gcd)return -1;
//
x=mul(x,c/gcd,bg)%bg;
ans+=x*M;
M*=bg;
ans=(ans%M+M)%M;
}
if(ans>=maxx)return ans;
else return ans+((maxx-ans)/M+((maxx-ans)%M ? 1 : 0))*M;
}
LL solve(){
LL x,y;
for(int i=1;i<=n;i++){
LL gcd=exgcd(atk[i],p[i],x,y);
LL c=a[i];
if(c%gcd!=0)return -1;
//
mod[i]=p[i]/gcd;
x=(mul(x,c/gcd,mod[i])+mod[i])%mod[i];
x0[i]=x;
maxx=max(maxx,a[i]/atk[i]+(a[i]%atk[i]?1:0));
}
LL ans=exCRT(mod,x0);
return ans;
}
int main(){
//freopen("1.in","r",stdin);
//freopen("1.out","w",stdout);
scanf("%d",&T);
while(T--){
init();
ans=solve();
printf("%lld\n",ans);
}
return 0;
}