CRT&EXCRT 中国剩余定理及其扩展

中国剩余定理(孙子定理)


有这样一个问题:
{ x ≡ a 1 ( m o d   m 1 ) x ≡ a 2 ( m o d   m 2 ) … … x ≡ a n ( m o d   m n ) \begin{cases} x\equiv a_1(mod~m_1)\\ x\equiv a_2(mod~m_2)\\ ……\\ x\equiv a_n(mod~m_n) \end{cases} xa1(mod m1)xa2(mod m2)xan(mod mn)
其中,m两两互质,求满足所有同余方程的解。

求解:
  • 废话不多说,高中数学书上有讲过一个很对的算法。比如当前对于 x ≡ a 1 ( m o d   m 1 ) x\equiv a_1(mod~m_1) xa1(mod m1)来说,我们找一个数 t 1 t_1 t1,让它是 m 2 − m n m_2-m_n m2mn的公倍数,并且让它在模 m 1 m_1 m1的意义下等于1。然后我们把它乘上 a 1 a_1 a1,在模意义下它就等于 a 1 a_1 a1了。其他的方程都这样做。我们再把所有的 a i ∗ t i a_i*t_i aiti加起来,这个和就是答案(当然可能还需要取模)。
  • 为什么是正确的?我们依次看看每一个同余方程条件是否满足就好了。对于第 i i i个方程,除了我们的 a i ∗ t i a_i*t_i aiti,其他的 a ∗ t a*t at都是 m i m_i mi的倍数,所以同余方程显然成立,所以我们的算法正确性显然。
  • 因此, x = ∑ i = 1 n a i ∗ t i %   M x=\sum_{i=1}^na_i*t_i\%~M x=i=1naiti% M,其中 M M M是所有数的 l c m lcm lcm
校oj1898 曹冲称象
  • 模板题,但wa了居然半天没找到错误……
  • 数据只保证两两互质,但没说是质数,拿费马小定理求逆元就死的明明白白了。
  • e x g c d exgcd exgcd求的 x x x可能是负数,记得取模。
  • e x g c d exgcd exgcd都不会写了,自闭了……

C o d i n g Coding Coding

#include
#define ll long long
using namespace std;
const int N=100;
int n,m[N],b[N];
ll M=1,ans;
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,x,y);
	ll tmp=x;x=y;y=tmp-a/b*x;
	return d;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;++i){
		scanf("%d%d",&m[i],&b[i]);
		M*=m[i];
	}
	for(int i=1;i<=n;++i){
		ll Mi,t,x,y;
		Mi=M/m[i];
		exgcd(Mi,m[i],x,y);
		ans=(ans+1LL*x*Mi*b[i])%M;
	}
	cout<<(ans+M)%M<<endl;
	return 0;
}

扩展版

  • 还是上面的问题,这回不保证 m m m两两互质了。我们先假设只有两个方程。用 e x g c d exgcd exgcd合并同余方程就好了。

C o d i n g Coding Coding

#include
#define ll long long
using namespace std;
const int N=1e5+10;
int n;ll m[N],a[N];
ll exgcd(ll a,ll b,ll &x,ll &y){
	if(!b){
		x=1;y=0;return a;
	}
	ll d=exgcd(b,a%b,x,y);
	ll tmp=x;x=y;y=tmp-a/b*y;
	return d;
}
ll mul(ll a,ll b,ll mod){
	ll res=0;
	for(;b;b>>=1){
		if(b&1) res=(res+a)%mod;
		a=(a+a)%mod;
	}
	return res;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;++i) scanf("%lld%lld",&m[i],&a[i]);
	ll k1,k2,m1,m2,a1,a2,c,d,x,y,x0;
	m1=m[1],a1=a[1];
	for(int i=2;i<=n;++i){
		m2=m[i],a2=a[i];
		c=a2-a1;c=(c%m[i]+m[i])%m[i];
		d=exgcd(m1,m2,x,y);
		if(c%d){
			printf("-1\n");
			return 0;
		}
		ll lc=m1/d*m2;
		k1=(mul(x,c/d,m2)+m2)%m2;
		x0=mul(k1,m1,lc)+a1;x0=(x0+lc)%(lc);
		m1=lc;a1=x0;
	}
	cout<<x0<<endl;
	return 0;
}

[NOI2018]屠龙勇士

  • 每次攻击时攻击力是确定的。设攻击力为 k k k,恢复力为 p p p,生命值 a a a,则有 k x = p y + a kx=py+a kx=py+a,类似于 k x ≡ a ( m o d   p ) kx\equiv a(mod~p) kxa(mod p)。我们中国剩余定理合并同余方程的时候, x x x的系数必须是1,所以想办法消掉 k k k。求逆元显然不合适,因为有没有逆元还不一定。
  • 可以求解一下这个同余方程啊。 k x + p y = a , x 0 = x ′ a d + k p d kx+py=a,x_0=x'\frac{a}{d}+k\frac{p}{d} kx+py=a,x0=xda+kdp,其中 x 0 x_0 x0是方程的通解。那么可以转化为同余方程 x 0 ≡ x ′ a d ( m o d   p d ) x_0\equiv x'\frac{a}{d}(mod~\frac{p}{d}) x0xda(mod dp)。此时就可以合并了。
  • 细节问题1:最终的 x x x一定要保证把龙杀死。也就是 x > m a x a a t k x>max{\frac{a}{atk}} x>maxatka,注意向上取整。
  • 细节问题2:一个是如果生命值和攻击力都是恢复力的倍数,显然你攻击次数任意,只要满足 x x x把龙杀死就好,不参与合并。
  • 细节问题3:如果生命值不是恢复力的倍数,而攻击力是,那么一定无解,直接输出-1。
  • 细节问题4:其实也不是什么细节,就是自己有点sb。 e x g c d exgcd exgcd求方程的解的时候, a , b a,b ab的顺序和对应的 x , y x,y xy的关系一定要搞好,否则错的你不明不白的。

C o d i n g Coding Coding

#include
#define ll long long
using namespace std;
const int N=1e5+10;
ll t,n,m,atk[N],p[N],a[N];
multiset<ll>s;
multiset<ll>::iterator it;
ll in(){
	char ch=getchar();ll num=0,f=1;
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){num=num*10+(ch^48);ch=getchar();}
	return num*f;
}
ll exgcd(ll a,ll b,ll &x,ll &y){
	if(!b){x=1;y=0;return a;}
	ll d=exgcd(b,a%b,x,y);
	ll tmp=x;x=y;y=tmp-a/b*x;
	return d;
}
ll mul(ll a,ll b,ll mod){
	ll res=0;
	for(;b;b>>=1){
		if(b&1) res=(res+a)%mod;
		a=(a+a)%mod;
	}
	return res;
}
int main(){
	t=in();
	E:while(t--){
		n=in(),m=in();ll temp;
		for(int i=1;i<=n;++i) a[i]=in();
		for(int i=1;i<=n;++i) p[i]=in();
		for(int i=1;i<=n;++i) atk[i]=in();
		s.clear();
		for(int i=1;i<=m;++i){temp=in();s.insert(temp);}
		ll k,d,tmp,lcm,x,y,c1=0,m1=1,c,m,M=0;
		for(int i=1;i<=n;++i){
			it=s.upper_bound(a[i]);
			if(it!=s.begin()) it--;
			k=*it,s.erase(it);
			s.insert(atk[i]);
			M=max(M,(a[i]-1)/k+1);
			k%=p[i];a[i]%=p[i];
			if(!k&&!a[i]) continue;
			if(!k&&a[i]){printf("-1\n");goto E;}
			d=exgcd(k,p[i],x,y);
			if(a[i]%d){printf("-1\n");goto E;}
			m=p[i]/d;c=(mul((x%m+m)%m,a[i]/d,m)+m)%m;
			d=exgcd(m1,m,x,y);tmp=c-c1;lcm=m1/d*m;tmp=(tmp+lcm)%lcm;
			if(tmp%d){printf("-1\n");goto E;}
			x=(mul(x,tmp/d,m/d)+m/d)%(m/d);
			c1=(mul(m1,x,lcm)+c1)%lcm;m1=lcm;
			//m1=m1/d*m;
			//c1=(c1+mul(mul(m1/m,((c-c1)%m1+m1)%m1,m1),(x%m1+m1)%m1,m1))%m1;
		}
		printf("%lld\n",c1>=M?c1:c1+m1*((M-c1-1)/m1+1));
	}
	return 0;
}

你可能感兴趣的:(数论)