1. 程式人生 > 其它 >[總結] (擴充套件)中國剩餘定理

[總結] (擴充套件)中國剩餘定理

中國剩餘定理

有物不知其數,三三數之剩二,五五數之剩三,七七數之剩二。問物幾何?

解線性方程組:

\(\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\)

P1495 【模板】中國剩餘定理(CRT)/曹衝養豬

#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;
}

擴充套件中國剩餘定理

P4777 【模板】擴充套件中國剩餘定理(EXCRT)

與中國剩餘定理的區別

擴充套件中國剩餘定理用來解決模數不互質的情況。

解法

採用合併答案的思想。
  • 如果我們解決了前 \(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}\)

有解,可以解得 \(t\)

現在考慮合併答案。

\(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());
}

例題

P4774 【NOI2018】 屠龍勇士

前言

中國剩餘定理起的是合併答案的作用。


題解

解同餘方程組:

\(\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;
}