孫子
Preface
數論學習Part 7。
每天進步一點點,退役不會太丟臉。
聯賽只剩十五天,隔靴搔癢智何添?
剩餘定理四天一篇,動歸圖論又幾何,數據結構似雲煙。
莫再等閒,莫忘時間。
CRT
我們小時候都做過這種問題,一個數x它滿足x≡⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧a1a2anmodp1modp2⋮modpn,然後讓你求出一個最小的x出來。要求所有的p互質。
遇到這種問題以前的我一般會各寫幾項出來,然後找相同。
這個時候孫子站了出來,他說出:記P=∏p,則x=∑k=1k=nakpkPtk,tk是pkP關於pk的逆元。這是x的一個解,如果你逆元是最小正逆元,那這個x就是最小正解。
爲什麼呢?
證明啊……。
先看x≡a1modp1。
因爲p1P與p1互質,所以肯定找得到數t1使得p1Pt1≡1modp1,這個t1被稱爲p1P關於p1的逆元。
那麼就有x≡a1≡a1×1≡a1p1Pp1modp1。
因爲p1P=p2×p3×⋯×pn,所以有a1p1Pp1≡0(modp2),(modp3),⋯,(modpn)。←不標準的表達,應當扣分
以此類推,a2p2Pt2就滿足模p2時與x同餘,模其它p的時候得到0。
那麼這些東西的和——s=∑k=1k=nakpkPtk,一定滿足s≡a1+0×(n−1)modp1,s≡a2+0×(n−1)modp2,s≡a3+0×(n−1)modp3,⋯,s≡an+0×(n−1)modpn,所以可以認爲s是原方程的一個解。那麼通解就是s+zP,z∈Z。
Q.E.D
就這?
代碼
洛谷板子題,雙倍經驗。
曹衝養豬
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
void Read(ll &p)
{
p=0;
int f=1;
char c=getchar();
while(c<'0' || c>'9')
{
if(c=='-') f=-1;
c=getchar();
}
while(c>='0' && c<='9')
p=p*10+c-'0',c=getchar();
p*=f;
}
void exgcd(ll a,ll b,ll &d,ll &x,ll &y)
{
if(!b){d=a,x=1,y=0; return;}
exgcd(b,a%b,d,y,x),y-=(a/b)*x;
}
ll n,ans,d,x,y,P=1,a[1024],p[1024];
int main()
{
Read(n);
for(ll i=1;i<=n;i++) Read(p[i]),Read(a[i]),P*=p[i];
for(ll i=1;i<=n;i++) exgcd(P/p[i],p[i],d,x,y),ans+=a[i]*(P/p[i])%p*x,((ans%=P)+=P)%=P;
[TJOI2009]猜數字。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
void Read(ll &p)
{
p=0;
int f=1;
char c=getchar();
while(c<'0' || c>'9')
{
if(c=='-') f=-1;
c=getchar();
}
while(c>='0' && c<='9')
p=p*10+c-'0',c=getchar();
p*=f;
}
void exgcd(ll a,ll b,ll &d,ll &x,ll &y)
{
if(!b){d=a,x=1,y=0; return;}
exgcd(b,a%b,d,y,x),y-=(a/b)*x;
}
ll mul(ll a,ll b,ll p)
{
return ((a*b-(ll)((long double)a/p*b+1e-8)*p)%p+p)%p;
}
ll n,ans,d,x,y,P,a[1024],p[1024];
int main()
{
Read(n),P=1;
for(ll i=1;i<=n;i++) Read(a[i]);
for(ll i=1;i<=n;i++) Read(p[i]),((a[i]%=p[i])+=p[i])%=p[i],P*=p[i];
for(ll i=1;i<=n;i++) exgcd(P/p[i],p[i],d,x,y),ans+=mul(mul(a[i],P/p[i],P),x,P),((ans%=P)+=P)%=P;
printf("%lld\n",ans);
}
EXCRT
現在這些p不一定互質了。
我們先看頭兩個方程吼,{x≡a1modp1x≡a2modp2。
那麼就有x=k1×p1+a1=k2×p2+a2⇒k1×p1−k2×p2=a2−a1。
令gcd(p1,p2)=g,則同時除就有k1gp1−k2gp2=ga2−a1⟺k1gp1≡ga2−a1modgp2,其中gcd(gp1,gp2)=1。
注意,如果g∤a2−a1,則無解。
因爲這兩個數互質,所以可以求出逆元來,就有:k1≡ga2−a1×(gp1)−1modgp2。
所以設k1=q×gp2+ga2−a1×(gp1)−1。
知道k1了,我們就知道了x。
x=k1×p1+a1=(q×gp2+ga2−a1×(gp1)−1)×p1+a1=q×gp1p2+ga2−a1×(gp1)−1×p1+a1
⟺x≡ga2−a1×(gp1)−1×p1+a1modgp1p2。
我們把兩個同餘式合成了一個,太棒了。
看來我們可以做很多次,最後得到一個方程x≡AmodB。
x肯定可以隨便解了。
代碼
洛谷板子題,【模板】擴展中國剩餘定理(EXCRT)。
居然一次過編譯過樣例,還直接A掉了。
我佛了。
太醜了。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
void Read(ll &p)
{
p=0;
int f=1;
char c=getchar();
while(c<'0' || c>'9')
{
if(c=='-') f=-1;
c=getchar();
}
while(c>='0' && c<='9')
p=p*10+c-'0',c=getchar();
p*=f;
}
ll gcd(ll a,ll b){return !b?a:gcd(b,a%b);}
void exgcd(ll a,ll b,ll &d,ll &x,ll &y)
{
if(!b){d=a,x=1,y=0; return;}
exgcd(b,a%b,d,y,x),y-=(a/b)*x;
}
ll mul(ll a,ll b,ll p)
{
return ((a*b-(ll)((long double)a/p*b+1e-8)*p)%p+p)%p;
}
ll n,A,B,C,D,E,d,p,x,y;
int main()
{
Read(n),n--,Read(B),Read(A);
while(n--) Read(D),Read(C),d=gcd(B,D),exgcd(B/d,D/d,p,x,y),E=B/d*D,(A+=mul(mul((C-A)/d,x,E),B,E))%=E,B=E;
printf("%lld\n",(A%B+B)%B);
}
CanKaoWenXian
CRT:其實是在我看了好幾篇舉個例子就說自己證好了的博客以後,再看到這篇博客的時候突然懂了,所以就以這篇博客爲參考文獻了。不過這篇文章確實給了我參考。
EXCRT
貞德真的好看。(不是貞德當我沒說)
說在後面
問了問數競的同學,他們的中國剩餘定理是證明其有解,然後數學歸納法就整出來了。
好吧,他們確實算不出來逆元。