中国剩余定理(孙子定理)
有这样一个问题:
{
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}
⎩⎪⎪⎪⎨⎪⎪⎪⎧x≡a1(mod m1)x≡a2(mod m2)……x≡an(mod mn)
其中,m两两互质,求满足所有同余方程的解。
求解:
- 废话不多说,高中数学书上有讲过一个很对的算法。比如当前对于 x ≡ a 1 ( m o d m 1 ) x\equiv a_1(mod~m_1) x≡a1(mod m1)来说,我们找一个数 t 1 t_1 t1,让它是 m 2 − m n m_2-m_n m2−mn的公倍数,并且让它在模 m 1 m_1 m1的意义下等于1。然后我们把它乘上 a 1 a_1 a1,在模意义下它就等于 a 1 a_1 a1了。其他的方程都这样做。我们再把所有的 a i ∗ t i a_i*t_i ai∗ti加起来,这个和就是答案(当然可能还需要取模)。
- 为什么是正确的?我们依次看看每一个同余方程条件是否满足就好了。对于第 i i i个方程,除了我们的 a i ∗ t i a_i*t_i ai∗ti,其他的 a ∗ t a*t a∗t都是 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=1nai∗ti% 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<bits/stdc++.h>
#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<bits/stdc++.h>
#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) kx≡a(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=x′da+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}) x0≡x′da(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 a,b的顺序和对应的 x , y x,y x,y的关系一定要搞好,否则错的你不明不白的。
C o d i n g Coding Coding
#include<bits/stdc++.h>
#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;
}