事情是這樣的,我是一個萌新,然后萌新初學數論。qvq
本篇文章的難度大概是gcd~莫比烏斯反演,說不定我還會寫一點組合計數,容斥原理,線性代數的知識,當然,我估計我不會,因為咕咕,同時,省選及以上的知識我會在聯賽后寫。
文章以數學證明為主,代碼都好理解,所有的運算以計算機運算法則為准。qvq(人家的碼風才不毒瘤)
GCD&LCM
即最大公約數和最小公倍數,這里介紹歐幾里得求gcd。
設(a,b)表示gcd(a,b)。
求證:gcd(a,b)=gcd(b,a%b)
證明:
設a=k1*c,b=k2*c且(k1,k2)=1.
則(a,b)=c.
設k3=a/b(按照計算機運算法則,向下取整)
則a%b=a-k3*b.
則有a%b=a-k3*b=k1*c-k3*k2*c=(k1-k2*k3)*c.
若(k2,k1-k2*k3)=1,(b,a%b)=c,則(a,b)=(b,b%a).
若(k2,k1-k2*k3)≠1,設k2=m1*d,k1-k2*k3=m2*d.
則(a,b)=(k3*m1*d*c+m2*d*c,m1*d*c)=dc≠c,則假設不成立。
綜上所述,(a,b)=(b,a%b).
證畢.
特別地,當b=0的時候,(a,b)=a。
LCM的求法:lcm(a,b)=a*b/gcd(a,b).就不證明了。
代碼:
#include<cstdio> int a,b; int gcd(int a,int b) { return (b==0)?a:gcd(b,a%b); } int lcm(int a,int b) { return a*b/gcd(a,b); } int main() { scanf("%d%d",&a,&b); printf("GCD=%d LCM=%d\n",gcd(a,b),lcm(a,b)); return 0; }
裴蜀定理&EXGCD
裴蜀定理:對於任意a,b∈Z,存在x,y∈Z滿足ax +by=gcd(a,b)。
裴蜀定理的特解和通解,可以通過exgcd(擴展歐幾里得)證明出來。
證明:
設c=gcd(a,b).
已知gcd(a,b)=gcd(b,a%b)=gcd(c,0).
假設裴蜀定理成立,則有:
a*x+b*y=b*x1+(a%b)*y1
=b*x1+(a-a/b*b)*y1
=b*x1+a*y1-a/b*b*y1
=a*y1+b*(x1-a/b*y1)
得到x=y1,y=x1-a/b*y1。
由此可知,方程一定在最后一層有解,即x=1,y=0(最后一層的特解)
綜上所述,裴蜀定理成立。
證畢.
哦對了,還沒解釋啥是exgcd,上述就是exgcd。
從上述證明我們能看出,裴蜀定理的證明是建立在exgcd基礎上的,exgcd可以用來解不定方程,方程有解的條件就是裴蜀定理。
代碼:
#include<cstdio> #define ll long long int a,b,c,x,y; ll exgcd(ll a,ll b) { if(b==0) { x=1,y=0; return a; } ll ans=exgcd(b,a%b); ll temp=x; x=y; y=temp-(a/b)*y; return ans; } int main() { scanf("%lld%lld%lld",&a,&b,&c); if(c%exgcd(a,b)==0) printf("%lld %lld\n",x*(c/exgcd(a,b)),y*(c/exgcd(a,b))); else printf("no solution\n"); return 0; }
當然,在此處推出的是不定方程的特解,我們如果需要通解該咋整呢?其實挺簡單的。若有不定方程ax+by=c且c | gcd(a,b),則方程通解為:
x=x0*(c/gcd(a,b))+k*(b/gcd(a,b)),y=y0*(c/gcd(a,b))+k*(a/gcd(a,b)),x0和y0是ax+by=gcd(a,b)的exgcd求解。
通解的正確性是很好證明的,把它代入原方程就可以了,這里不再證明。
那么如果我們要求的是最小正整數解呢(事情真多),也還是很好處理嘛。
設A=b/gcd(a,b),B=a/gcd(a,b),則有xmin=(x%A+A)%A,ymin=(y%B+B)%B。
眾所周知,我不想證,因為很懶。
放兩道題:
同余問題
同余問題會講得非常非常詳細,因為知識點多,專業語言多,而且,很毒瘤。
前置知識
0.a≡b (mod m) 表示 a%m=b%m
1.在0的條件下,有m | a-b
2.在0的條件下,有ax≡bx (mod m) (x∈Z)
3.在0的條件下,若有c≡d (mod m),則有a+c≡b+d (mod m),有ac≡bd (mod m)
4.若有ab≡ac (mod m) 且有(a,m)=1,則有b≡c (mod m)
5.對於任意實數a,它在%m的情況下的答案在0,1...a-1當中,那么我們根據不同的答案將所有實數分為不同集合,每一個集合我們將其稱為一個剩余系,表示為[0],[1],.....[a-1]。那么有[ i ]+[ 0 ]=[ i ],[ i ]*[ 0 ]=[ 0 ],[ i ]*[ 1 ]=[ i ],這就是剩余系意義下的一些運算法則,非常好證明,大家可以自己嘗試一下,若我們%m,那么{[1],[2]...[m-1]}就稱為m的完全剩余系,[a]中的a稱為代表元,而如果我們從完全剩余系當中找出和m互質的剩余系,比如%5,和它互質的剩余系是[1],[2],[3],我們將這些剩余系放到一個集合中,稱為簡化剩余系。
費馬小定理
ap-1≡1 (mod p) (p為質數且a不為p的倍數)
證明:
由已知:p為質數且a不為p的倍數
則(a,p)=1
p的完全剩余系K:{[1],[2],[3]......[p-1]}
若將K*a,得到:
{[1]*a,[2]*a,[3]*a......[p-1]*a}
假設a*[1]*a*[2]≡[1]*[2] (mod m)不成立 那么不滿足同余的基本性質4(上文),所以成立,那么該式推廣可得:
a*[1]*a*[2]*a*[3]*...*a*[p-1]≡[1]*[2]*[3]*....[p-1] (mod m)
<=> ap-1*[1]*[2]....*[p-1]≡[1]*[2]*...*[p-1] (mod m)
<=>ap-1≡1 (mod m)(該式由同余的基本性質4得到)
證畢.
歐拉定理
aφ(m)≡1(mod m) (a與m互質) 其中φ(m)表示m的簡化剩余系個數。
證明:
設φ(m)={[1]..[m-1]}
若將φ(m)*a,得到:
{a*[1]...a*[m-1]}
和費馬小定理的證明過程相同,可以得到:
a*[1]*...a*[m-1]≡[1]*...[m-1] (mod m)
<=> aφ(m)≡1(mod m)
證畢.
擴展歐拉定理
Ⅰ.當b<φ(m) ab≡ab (mod m) (a,m可以不互質)
Ⅱ.當b>=φ(m) ab≡ab%φ(m)+φ(m) (mod m) (a,m可以不互質)
證明Ⅱ:
設b=k*φ(m)+r
已知aφ(m)≡1 (mod m)
則(aφ(m))k≡1 (mod m)(由同余的基本性質2得)
所以(aφ(m))k % m=1
則有(aφ(m))k% m *ar = ar
該式變換得到:aφ(m)*k+r≡ar (mod m)
那么 ab≡ab%φ(m)+φ(m) (mod m)
證畢.
乘法逆元
乘法逆元也分為普通的乘法逆元和剩余系中的乘法逆元,但是在OI當中我們主要用的還是普通版本,那么到底什么是乘法逆元呢?
給定計算機意義下的b/a,求(b/a)%m的值。
很多同學可能會覺得,這個不就是(b%m/a%m)%m嗎?如果各位同學真的這么覺得,那我建議還是從c++基本語法學起,這一條不屬於模運算的運算法則。
這個時候,我們就要引入一個新的概念——逆元。
當我們求(b/a)%m的時候,如果a|b這個還好說,不整除該怎么辦呢?這里我們設一個x,使ax≡1 (mod m),那么這個x就相當於a在%m意義下的倒數。這個時候我們只需要求出x,再用x*a%m,就求到了這個值。但是這玩意咋求呢?方法很多,我這里給出exgcd的求法。
exgcd
解:
已知:存在x使ax≡1(mod m)
設ax=k1*m+r,1=k2*m+r
則ax-1=(k1-k2)*m
移項得到:ax+(k2-k1)*m=1
則可以用擴展歐幾里得求解。
逆元應該是exgcd的最小正整數解。
到這里,相信大家就明白啦。
代碼:
#include<cstdio> #include<algorithm> #include<iostream> using namespace std; long long a,p,x,y; void exgcd(long long a,long long p) { if(!p) { x=1; y=0; return ; } exgcd(p,a%p); long long k; k=x; x=y; y=k-(a/p)*y; } int main() { scanf("%lld%lld",&a,&p); for(int i=1;i<=a;i++) { exgcd(a,p); printf("%lld\n",(x%p+p)%p); } return 0; }
線性遞推
關於求逆元,其實還有一種方法叫做線性遞推,inv[i]表示的就是i的逆元,也是用同余的知識+代數余來證明的,我不再寫了,有興趣的同學可以自己證一下,比exgcd的證明還簡單。
#include<cstdio> using namespace std; const int N=3e6+5; #define ll long long ll ans[N]={0,1},a,p; int main() { scanf("%lld%lld",&a,&p); printf("1\n"); for(int i=2;i<=a;i++) { ans[i]=(p-p/i)*ans[p%i]%p; printf("%lld\n",ans[i]); } return 0; }
費馬小定理求逆元
想不到吧?費馬小定理也能求逆元。
am-1≡1 (mod m) 轉化成ax≡1 (mod m)
這下傻子都看得出來x=am-2.......
快速冪搞一搞,O(logn)引起舒適。
代碼:
#include<cstdio> using namespace std; #define int long long int a,b,m; int pow(int a,int b) { int ans=1; while(b) { if(b&1) ans=ans*a%m; a=a*a%m; b>>=1; } return ans; } signed main() { scanf("%lld%lld%lld",&a,&b,&m); int inv=pow(b,m-2); printf("%lld\n",(a*inv%m+m)%m); return 0; }
2019.10.12 update
今天對乘法逆元有了一些新的理解,前來更新一波,主要是遇到了兩個有趣的東西。
1.求n!/(∏mi=1ci!)(n<=1e6 ci<=1e6 ci<=n)(mod 19260817)
首先,n!到20+的時候應該就跑不動了,所以對於n,我們可以邊乘邊取模,正確性顯然。
(某C姓教練:你不管那么多,只要是加減乘你隨便亂%就可以了!)
但是,求解逆元的這個過程也可能會跑不動,因為ci也很大,考慮逆元x的求解 ax≡1 (mod m),在同余式的兩邊就算先同時取模也毫無影響。
而關於ci的求解,你考慮費馬小定理求逆元,ci在求n的求解過程中已經取模,那你直接求逆元不就可以了嗎?
放部分代碼
fac[0]=1,inv[0]=1; for(int i=1;i<=n;i++) fac[i]=(fac[i-1]*i)%mod,inv[i]=pow(fac[i],mod-2); int ans=fac[n]; for(int i=1;i<=m;i++) ans=(ans*inv[a[i]]%mod+mod)%mod; printf("%lld\n",ans);
2.(a/b)%m (1<=a,b<=10^20000) (m=19260817)
其實就是Luogu上的這道題 P2613 【模板】有理數取余
qvq從這道神奇的題目我發現,我可以亂搞...原來除法取模一點也不嚴謹,隨便亂搞都可以。
對a和b瘋狂取模,然后求b的逆元,求解,over。
完了...完了...完了...
還是道藍題...水到我顫抖。
代碼:
#include<cstdio> #include<cstring> using namespace std; #define int long long #define p 19260817 int pow(int a,int b) { int ans=1; while(b) { if(b&1) ans=ans*a%p; a=a*a%p; b>>=1; } return ans; } inline int read(int&x) { x=0; char c=getchar(); while(c<'0'||c>'9') c=getchar(); while(c>='0'&&c<='9') x=(x*10+c-'0')%p,c=getchar(); return x; } signed main() { int a,b; read(a),read(b); if(b==0){printf("Angry!\n");return 0;} int inv=pow(b,p-2); printf("%lld\n",(a*inv%p+p)%p); return 0; }
歐拉函數
歐拉函數,是積性函數的一種,其表達式為φ(N),表示1~N當中與m互質的數的個數。
φ(N)=N*∏prime p|N(1-1/p)
證明:
根據整數的唯一分解定理,設N=p1^k1*p2^k2*.....pn^kn,那么N就被分成了n個不同的質數的ki次方相乘。
如果p1是N的一個質因數,那么我們就可以把<=N的p1的倍數全部從N當中篩去,也就是說p1,p1*2...p1*(N/p1)都應該從中篩去,即N-(N/p1)。
如果p2是N的一個質因數,那么我們就可以把<=N的p2的倍數全部從N當中篩去,也就是說p2,p2*2...p1*(N/p2)都應該從中篩去,即N-(N/p2)。
在這個過程中,p1*p2及其倍數被多篩了一次,應該加回來,於是篩去p1和p2的結果為N-(N/p1)-(N/p2)+(N/(p2*p1))。
化簡得到:N*(1-1/p1)*(1-1/p2)
我們可以推廣到:N*∏prime p|N(1-1/p)
證畢.
代碼:
#include<cstdio> int m; int main() { scanf("%d",&m); int temp=m,phi=m; for(int i=2;i*i<=m;i++) { if(!(temp%i)) phi=phi*(i-1)/i; while(!(temp%i)) temp/=i; } if(temp>1) phi=phi*(temp-1)/temp; printf("%d\n",phi); }