一、基本的思想和定理。
算数基本定理,也叫唯一分解定理。指的是一个大于一的合数都可以分解为有限个质数相乘。即N=p1a1p2a2*.....*pnan。p即为质数,a为质数的指数。
二、一些重要的公式。
1、求约数个数和约数的和。
给定一正整数n,求n的所有约数的个数。公式:num=(a1+1)*(a2+1)*......*(an+1)。至于原理,我理解的思路是,按照组合原则,p1a1一共有(a1+1)种情况,其余的以此类推。
2、求约数个数的和。
给定一个正整数n,求n的所有约数之和。公式:sum=(1+p11+p12+.....+p1n)*(1+p21+p22+.....+p2n)*......*(1+pn1+pn2+...+pnn)。原理就是,把n的约数和分解为pa的约数和。
3、欧拉函数。
φ(n)表示为某一个正整数的欧拉函数:小于n的正整数中和n互质的正整数的个数。
欧拉函数具有以下三个特性:
// 1、φ(n)=n*(1-1/p1)*(1-1/p2 )*(1-1/p3)*.....*(1-1/pn);
// 2、若m为质数,n%m==0,则φ(m*n)=m*φ(n);
// 3、若m,n互质,则φ(n*m)=φ(m)*φ(n)。
三、C++代码。
1、如何判断素数。
// 常规的方法是判断n是否能被2到n之间的数整除,如果能够整除,则说明n有除了1以外的其他因数,则n不是质数。
优化后的思路是:假如存在一个数(不一定为正整数)i,使得i*i=n,则我们可以从2到i之间的数进行判断,因为两个数之间成反比关系,一个肯定大于等于i,另一个肯定小于等于i。那么,只要保证在2到i之间没有正整数可以整除n,我们就可以知道,n一定为质数。
bool is_prime(int n) { for (int i = 2; i * i <= n;i++){ if(n%i==0){ return false; } } return true; }
2、如何分解质因数。
// 分解质因数首先从最小的质数开始分解,如果能够被整除,则一直循环到不能被整除为止,在此期间还可以计算质因数出现的次数。
同样也可以进行优化,和如何判断质数的优化方法一样,如果有这样的数i,我们就可以在<=i处提前判断完成,因为在判断前面的质数之后,大于i的部分不可能会有质数再被整除。
除非当我们整除这些质数之后的n,本身就是一个比较大的质数,这时候,我们可以在最后判断n是否大于1,是的话,n即为质数。比如1004=2^2*251。在我们枚举2之后,n的值就变成了251,这时候从3到sqrt(251,1/2)之间都没有数整除n。
vector<pair<int,int>> v; int Prime_factorization(int n) { for (int i = 2; i * i < n;i++){ if(n%i==0){ int s = 0; while(n%i==0){ n /= i; s++; v.push_back({i, s}); } } } if(n>1){ v.push_back({n, 1}); } }
// 如果对动态数组不熟悉,可以尝试用结构来构建。
3、筛选素数之埃式筛选法(E_sieve)
// 埃式筛选的原理也很简单,在我们找到一个素数之后,素数的倍数一定不是素数。根据这一原则,我们就可以进行筛选了。
优化的地方和上面一样,首先是找素数的过程,到i处就可以了。然后是在素数的倍数方面,当i=5时,2*5,3*5,4*5已经在前面优化过了。这时候从5*5开始筛选就可以了。
时间复杂度O(nloglogn)
int primes[maxx]; bool vis[maxx]; void E_sieve(int n) { memset(vis, 0, sizeof(vis)); int k = 0; for (int i = 2; i * i <= n;i++){ if(!vis[i]){ primes[k++] = i; for (int j = i * i; j <= n;j += i){ vis[primes[j] * i] = true; } } } }
4、筛选素数之线性筛(Linear_sieve)
// 线性筛主要是针对埃筛的一些问题进行解决,以使得时间复杂度降低。埃筛的主要问题是一个数可以被两个质数筛选。即使是优化后的代码。比如12被2和3两个质数筛选。
优化方面会有一些难懂。推荐先记忆模板,再慢慢理解。
优化的地方在:我们在筛选的时候,让合数被最小质因数筛选。加一个判定条件就可以了。
时间复杂度O(n)
int primes[maxx]; bool vis[maxx]; void Linear_sieve(int n) { int k = 0; for (int i = 2; i * i <= n;i++){ if(!vis[maxx]){ primes[k++] = i; } for (int j = 0; primes[j] * i <= n;j++){ vis[primes[j] * i] = true; if(i%primes[j]==0){ break; } } } }
对于第二个for循环里的if语句,就是说,如果i可以整除primes[j],那么primes[j+1]一定不是primes[j+1】*i的最小质因数。比如i=2,primes[0]=2;2%2=0,下一个质数为3,而2*3的最小质因数为2,因此break;
// 可以自己手动列式子理解一下。
5、筛选素数之欧拉筛。//放到后面去讲
6、求所有约数
// 和找质因数一样的道理。
vector<int> v; void Divisor_find(int n) { for (int i = 1; i * i <= n;i++){ if(n%i==0){ v.push_back(i); if(i!=n/i){ v.push_back(n / i); } } } }
7、求所有约数的个数。
// 根据公式求就可以了。分解质因数记录质数的指数。
int cnt[maxx]; int k = 0; int Number_of_divisor(int n) { for (int i = 2; i * i <= n;i++){ if(n%i==0){ int s = 0; while(n%i==0){ n /= i; s++; } cnt[k++] = s; } } int sum = 0; for (int i = 0; i < k;i++){ sum += (cnt[i] + 1); } return sum; }
8、求所有约数的和。
// 一样的道理,根据公式求即可。不过要注意的是,可能会用到费马小定理。但是这里我们还是要注意一个公式就是x/y%mod可以转换为x%(y*mod)/y;
ll quickpow(ll a,ll b) { ll res = 1; while(b){ if(b&1){ res = (res * a); } b >>= 1; a = (a * a); } return res; } int a[maxx], b[maxx]; int k = 0; int Sum_of_divisor(int n) { for (int i = 2; i * i <= n;i++){ if(n%i==0){ int s = 0; while(n%i==0){ n /= i; s++; } a[k] = i; b[k] = s; k++; } } if(n>1){ a[k] = n; b[k] = 1; k++; } int sum; for (int i = 0; i < k;i++){ sum += (quickpow(a[i], b[i] + 1) - 1) / (1 - a[i]); } return sum; }
9、欧拉函数
// n*(i-1/p)可以换成n/p*(p-1)。然后按公式写就可以了。
int Euler_function(int n) { int res = n; for (int i = 2; i * i <= n;i++){ if(n%i==0){ res = res / i * (i - 1); while(n%i==0){ n /= i; } } } if(n>1){ res = res / n * (n - 1); } return res; }
10、欧拉筛
// 根据欧拉函数三个特性写
int primes[maxx], phi[maxx]; bool vis[maxx]; void Euler_sieve() { memset(vis, 0, sizeof(vis)); int cnt = 0; for (int i = 2; i * i <= n;i++){ if(!vis[i]){ primes[cnt++] = i; phi[i] = i - 1; } for (int j = 0; primes[j] * i <= n;j++){ vis[primes[j] * i] = true; if(i%primes[j]==0){ phi[primes[j] * i] = primes[j] * phi[i]; break; } else{ phi[primes[j] * i] = phi[i] * (primes[j] - 1); } } } }