事情是这样的,我是一个萌新,然后萌新初学数论。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 ",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 ",x*(c/exgcd(a,b)),y*(c/exgcd(a,b))); else printf("no solution "); 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 ",(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 "); for(int i=2;i<=a;i++) { ans[i]=(p-p/i)*ans[p%i]%p; printf("%lld ",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 ",(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 ",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! ");return 0;} int inv=pow(b,p-2); printf("%lld ",(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 ",phi); }