数论基础知识汇总
一 初级预备知识
1.唯一分解定理:把正整数n写成质数的乘积(即n=p1p2p3...pk,其中pi为质数且单调不减),这样的表示是唯一的。
•例题:[CF 776B]Sherlock and his girlfriend
•n个点,标号2..n+1,
•给这些点染色,要求若a是b的质因子,则a和b的颜色不同。
•求一种颜色数最少的方案
解:我们只需要2种颜色即可,质数一种颜色,合数一种颜色就可以了。
2.整除
设a,b是两个正整数,且b!=0,则存在唯一的整数q和r,使 a=qb+r,这个式子叫做带余除法
性质:
•1整除任何数,任何数都整除0
•若a|b,a|c,则a|b+c, a|b-c
•若a|b,则对任意整数c,a|bc
•传递性:若a|b,b|c,则a|c
3.约数
对于一个大于1正整数n可以分解质因数:n=p1^a1*p2^a2*p3^a3*…*pk^ak,
则由约数个数定理可知n的正约数有(a₁+1)(a₂+1)(a₃+1)…(ak+1)个,
那么n的(a₁+1)(a₂+1)(a₃+1)…(ak+1)个正约数的和为
f(n)=(p1^0+p1^1+p1^2+…p1^a1)(p2^0+p2^1+p2^2+…p2^a2)…(pk^0+pk^1+pk^2+…pk^ak)
看计算约数模板之前还有学会素数筛,筛法有很多,二重暴力O(n^2),埃氏筛法,O(nloglogn),欧拉筛O(n).显然欧拉筛是时间复杂度最低的。
欧拉筛模板:
1 for (int i=2;i<=n;i++) 2 { 3 if (!pri[i])ans[++tot]=i; 4 for (int j=1;(j<=tot)&&(i*ans[j]<=n);j++) 5 { 6 pri[i*ans[j]]=1; 7 if (i%ans[j]==0)break; 8 } 9 }
我觉得这个博客讲的比较好:https://blog.csdn.net/qq_39763472/article/details/82428602
很难懂的一个点:
if (i%ans[j]==0)break;
其实打个表出来看看会发现其实意思就是一个非素数只能用它的最小质因数筛掉,避免重复筛,以达到O(n)的复杂度。
1 #include<bits/stdc++.h> 2 #define inf 1000000000 3 #define ll long long 4 #define N 10000010 5 using namespace std; 6 inline int read() 7 { 8 int x=0;char ch=getchar(); 9 while (ch<'0'||ch>'9') ch=getchar(); 10 while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); 11 return x; 12 } 13 int pd[N+10],pri[N]; 14 ll e[N+10],t[N+10],sum[N+10]; 15 void oula() 16 { 17 for (int i=2;i<=N;i++) 18 { 19 if (!pd[i]) {t[i]=i+1;e[i]=1;pri[++pri[0]]=i;} 20 for (int j=1;j<=pri[0]&&pri[j]*i<=N;j++) 21 { 22 pd[pri[j]*i]=1; 23 if (i%pri[j]==0) 24 { 25 t[i*pri[j]]=t[i]*pri[j]+e[i]; 26 e[i*pri[j]]=e[i]; 27 break; 28 } 29 t[i*pri[j]]=t[i]*(pri[j]+1),e[i*pri[j]]=t[i]; 30 } 31 } 32 } 33 int main() 34 { 35 oula();t[1]=e[1]=1; 36 for (int i=1;i<=N;i++) sum[i]=sum[i-1]+t[i]; 37 int T=read(); 38 while (T--) 39 { 40 ll n=read(); 41 printf("%lld\n",n*n-sum[n]); 42 } 43 return 0; 44 }
1 #include<cstdio> 2 3 using namespace std; 4 5 #define N 1000001 6 7 bool vis[N]; 8 int prime[N]; 9 10 int t[N],e[N]; 11 12 int main() 13 { 14 int n; 15 scanf("%d",&n); 16 int cnt=0; 17 t[1]=1; 18 for(int i=2;i<=n;++i) 19 { 20 if(!vis[i]) 21 { 22 prime[++cnt]=i; 23 t[i]=2; 24 e[i]=1; 25 } 26 for(int j=1;j<=cnt;++j) 27 { 28 if(i*prime[j]>n) break; 29 vis[i*prime[j]]=true; 30 if(i%prime[j]==0) 31 { 32 t[i*prime[j]]=t[i]/(e[i]+1)*(e[i]+2); 33 e[i*prime[j]]=e[i]+1; 34 break; 35 } 36 else 37 { 38 t[i*prime[j]]=t[i]*2; 39 e[i*prime[j]]=1; 40 } 41 } 42 } 43 long long ans=0; 44 for(int i=1;i<=n;++i) ans+=t[i]; 45 printf("%lld",ans); 46 }
二.正题
1.欧几里得:
1 int gcd(int a,int b) 2 { 3 return b==0?a:gcd(b,a%b); 4 }
2.扩展欧几里得:
1 void exgcd(ll a,ll b,ll &x,ll &y) 2 { 3 if(b==0){x=1;y=0;return;} 4 exgcd(b,a%b,x,y); 5 ll t=x;x=y;y=t-a/b*y; 6 }
3.费马小定理:
若 p 是质数, a 与 p 互质,那么a p − 1 ≡ 1 ( m o d p )
4.逆元
定义:如果a·x≡1(mod p)且gcd(a,p)=1(即a,p互质),那么x为模p意义下的乘法逆元。
一个数有逆元的充分必要条件是gcd(a,p)=1,此时逆元唯一存在。
一般有两种方法求逆元:
①扩展欧几里得求逆元:
1 typedef long long ll; 2 ll exgcd(ll a, ll b, ll &x, ll &y) //扩展欧几里得算法 3 { 4 if (b == 0) 5 { 6 x = 1, y = 0; 7 return a; 8 } 9 ll t = exgcd(b, a % b, y, x);//注意是y,x 10 y = y - a / b * x; 11 return t; 12 } 13 ll Inv(int a, int mod) //求a在mod下的逆元,不存在逆元返回-1 14 { 15 ll x, y; 16 ll d = exgcd(a, mod, x, y); 17 return d == 1 ? (x%mod+ mod) % mod : -1; //x可能是负数,转化成正数 18 }
②费马小定理求逆元:
ll qpow(ll a, ll b) { ll res = 1; while (b) { if (b & 1) res = res * a % mod; a = a * a % mod; b >>= 1; } return res % mod; } // 费马小定理 —— a在模p意义下的逆元 ll Inv(ll a, ll p) { return qpow(a, p - 2); }
5.线性同余方程组:
1>中国剩余定理:只用来解模数互质情况,参见http://blog.miskcoo.com/2014/09/chinese-remainder-theorem
1 //chu是除数,yu是余数 2 //注意只适用于除数两两互质 3 #include<iostream> 4 #include<queue> 5 using namespace std; 6 typedef long long ll; 7 ll extended_euclid(ll a, ll b, ll &x, ll &y) { 8 ll d; 9 if(b == 0) {x = 1; y = 0; return a;} 10 d = extended_euclid(b, a % b, y, x); 11 y -= a / b * x; 12 return d; 13 } 14 ll chinese_remainder(ll b[], ll w[], ll len) { 15 ll i, d, x, y, m, n, ret; 16 ret = 0; n = 1; 17 for(i=0; i < len ;i++) n *= w[i]; 18 for(i=0; i < len ;i++) { 19 m = n / w[i]; 20 d = extended_euclid(w[i], m, x, y); 21 ret = (ret + y*m*b[i]) % n; 22 } 23 return (n + ret%n) % n; 24 } 25 ll yu[100],chu[100]; 26 int main() 27 { 28 ll n; 29 while(cin>>n) 30 { 31 for(ll i=0;i<n;i++) 32 { 33 cin>>chu[i]>>yu[i]; 34 } 35 ll ans=chinese_remainder(yu,chu,n); 36 cout<<ans<<endl; 37 } 38 return 0; 39 }
2>扩展剩余定理:解模数可以不互质情况,证明参见:http://www.cnblogs.com/zwfymqz/p/8425731.html
1 #include<cstdio> 2 #include<iostream> 3 #define ll long long 4 using namespace std; 5 inline ll read() 6 { 7 ll x=0,f=1;char ch=getchar(); 8 while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} 9 while(ch>='0'&&ch<='9'){x*=10;x+=ch-'0';ch=getchar();} 10 return x*f; 11 } 12 int k; 13 ll a1,b1,a2,b2; 14 inline ll gcd(ll a,ll b){return b==0?a:gcd(b,a%b);} 15 void exgcd(ll a,ll b,ll &x,ll &y) 16 { 17 if(b==0){x=1;y=0;return;} 18 exgcd(b,a%b,x,y); 19 ll t=x;x=y;y=t-a/b*y; 20 } 21 int main() 22 { 23 k=read(); 24 a1=read();b1=read(); 25 ll a,b,c,x,y; 26 for(int i=1;i<k;i++) 27 { 28 a2=read();b2=read(); 29 a=a1;b=a2;c=b2-b1; 30 ll t=gcd(a,b); 31 if(c%t){printf("-1\n");return 0;} 32 else 33 { 34 a/=t;b/=t;c/=t; 35 exgcd(a,b,x,y); 36 x=((c*x)%b+b)%b; 37 b1=b1+a1*x; 38 a1=a1*b; 39 } 40 } 41 printf("%lld\n",b1); 42 return 0; 43 }
6.判断两条线段是否相交:
分两步:①快速排斥 ②跨立实验(矢量叉积)
参考:[https://blog.csdn.net/HelloZEX/article/details/80880385]
1 double calcu(node a,node b) 2 { 3 double X=a.x-b.x; 4 double Y=a.y-b.y; 5 return sqrt(X*X+Y*Y); 6 } 7 double getcross(node a,node b,node c) 8 { 9 return (c.x-a.x)*(b.y-a.y)-(b.x-a.x)*(c.y-a.y); 10 } 11 bool is_crossing(node a,node b,node c,node d) 12 { 13 if (!(min(a.x,b.x)<=max(c.x,d.x) && min(c.x,d.x)<=max(a.x,b.x) && min(a.y,b.y)<=max(c.y,d.y) && min(c.y,d.y)<=max(a.y,b.y))) 14 return false; 15 double u,v,w,z; 16 u=getcross(a,b,c);v=getcross(a,b,d); 17 w=getcross(c,d,a);z=getcross(c,d,b); 18 return (u*v<=0.00000001 && w*z<=0.00000001); 19 }
待续......