一、约数个数
算数基本定理:一个合数可以分解为质因数的乘积,如果无论顺序的话,那么此分解唯一。
约数个数定理:根据算数基本定理可以得到质因数,然后相同的质因数进行合并,然后根据约数定理我们可以知道
p(m) ^ a(m) :pm的am次方,它的约数个数有am+1个,又根据乘法定理得到n的约数个数就是(a1+1) * (a2 + 1) * …* (am + 1);
#include <bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
int main(void){
int n;
unordered_map<int,int> mp;
cin>>n;
for(int i=0;i<n;i++){
int a;
cin>>a;
for(int j=2;j<=a/j;j++){
while(a%j==0){
mp[j]++;
a/=j;
}
}
if(a>1){
mp[a]++;
}
}
long long res=1;
for(auto it:mp){
res=res*(it.second+1);
res%=mod;
}
cout<<res;
return 0;
}
二、约数之和
约数和定理:就是求一个正整数的约数之和
由算数基本定理可知:n = p1^a1 * p2^a2 * p3^a3 * … * pm^am。
又由约数个数定理可知:d(n) = (a1+1) * (a2 + 1) * …* (am + 1)。
约数个数之和就是n的d(n)个正约数的和为:
12 = 2 * 2 * 3 = 2^2 + 3^1。
由约数定理可知:pm^am的约数是 pm^0 + pm^1 + pm^2 … pm^am。
2^2的约数 2^0, 2^1, 2^2
约数之和 σ(n) = (p1^0 + p1^1 + p1^2 + … + p1^a1) * (p2^0 + … + p2^a2) * … * (pm^0 + … + pm^am)。
约数的每一项就是一个等比数列的前a1+1项之和,根据等比数列前n项和可以化简。S(n) = (a1 - an*q) / (1 - q)
#include <bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
int main(void){
int n;
unordered_map<int,int> mp;
cin>>n;
for(int i=0;i<n;i++){
int a;
cin>>a;
for(int j=2;j<=a/j;j++){
while(a%j==0){
a/=j;
mp[j]++;
}
}
if(a>1){
mp[a]++;
}
}
long long res=1;
for(auto it:mp){
long long t=1,prime=it.first,cnt=it.second;
while(cnt--){
t=t*prime+1;
t%=mod;
}
res=res*t;
res%=mod;
}
cout<<res;
return 0;
}
三、快速幂
正常求a^b的方法是迭代,但是效率太低,时间复杂度为O(n),所以就有了快速幂。
快速幂的基本思想是二进制。 将指数用化为二进制表示,那么就只剩下logb + 1位,然后如果能够在小于O(logn)的时间复杂度内建立出一张20~2(log b +1)的表的话,就能在O(log n)的时间复杂度内求出a^b
int qmi(long long a,long long b,long long p){
long long res=1;
while(b){
if(b&1){
res=res*a%p;
}
b>>=1;
a=a*a%p;
}
return res;
}
四、最大公约数
1、辗转相除法
设两个正整数为a , b, 假设 a > b;
- a | b 那么 我们a就是b的约数,那么a又是a的最大约数,所以a就是a和b的最大公约数 (a % b == 0)
- a 不整除 b, 想办法找到它的公约数
int gcd (int a, int b){
return b == 0 ? a :gcd(b, a%b);
}
为了避免递归调用产生问题,我们可以使用循环来重写
//怎么将递归写成循环 //非递归版本
int gcd (int a, int b){
if(a < b) swap(a, b);
while( b != 0 ){ //或者写成!b
int tmp = a;
a = b;
b = temp % b; //相当于递归中的a % b
}
return a;
}
2、更相减损术
gcd(a, b) = gcd(b, a-b)
证明:
d | a, d | b等价于d | (a - b)
根据我们的带余除法
a = d * q1 + r;
b = d * q2 + r;
那么a - b = d(q1 - q2);
又因为d|a , d|b,这里的q1 - q2肯定是整数,所以d|(a-b) ==》同余性质
int gcd(int a, int b){
if(a < b) swap(a, b);
return b == 0 ? a : gcd(max(b, a - b), mix(b, a-b));
}
//非递归写法
int gcd(int a, int b){
if(a < b) swap(a, b);
while( !b ){
int tmp = a;
a = max(b, a - b);
b = min(b, a - b);
}
}
3、最小公倍数
在数学上有两种计算方法,一是分解质因数法,二是公式法
分解质因数法:
先把这几个数的质因数写出来,最小公倍数等于它们所有的质因数的乘积(如果有几个质因数相同,则比较两数中哪个数有该质因数的个数较多,乘较多的次数)
公式法:
两个数的乘积=这两个数的最大公约数×这两个数的最小公倍数。即(a,b)×[a,b]=a×b。所以,求两个数的最小公倍数,就可以先求出它们的最大公约数,然后用上述公式求出它们的最小公倍数。
1、题目详情:P1029 [NOIP2001 普及组] 最大公约数和最小公倍数问题
输入两个正整数 x 0 , y 0 x_0, y_0 x0,y0,求出满足下列条件的 P , Q P, Q P,Q 的个数:
- P , Q P,Q P,Q 是正整数。
- 要求
P
,
Q
P, Q
P,Q 以
x
0
x_0
x0 为最大公约数,以
y
0
y_0
y0 为最小公倍数。
试求:满足条件的所有可能的 P , Q P, Q P,Q 的个数。
分析:
12 和 15
12 = 2^2 * 3^1
15 = 3^1 * 5^1
//约掉最大公约数
//60是最小公倍数
60 = 3^1 * 2^2 * 5^1
3 = 3^1
12 = 3^1 * 2^2
15 = 3^1 * 5^1
假如我们最后求出来有n个素因数
那么这n个素因数都可以放任意一个数中
1 2 3 4 5 ... n
2 2 2 2 2 ... 2
2^n
2^2 * 5^1 = 60/3;
//所以它的这两个实际就是除去最大公约数的素因数的组合
//比如我想造一个最大公约数为12的数
根据算术基本定理,我知道素因数的分解一定是唯一的
所以我只需要每个数造12的素因数,剩下的数我随便写都会是最大公约数为12的数
2^2 * 3^1
2^2 * 3^1
接下来你就是想要这两个的最小公倍数是你给定的数
比如题目给的3 和 60
//又因为约掉最大公约数之后所有数的乘积就是最小公倍数
3^1 * 2^2 * 5^1
3^1
num1:
num2:
num1 * num2 = 60 / 3 = 20
2^2 5^1
2^2*5^1 1
接下来我们只需要找到那些素因数即可
#include <bits/stdc++.h>
using namespace std;
long long x,y;
long long gcd(long long x,long long y){
if(y==0) return x;
return gcd(y,x%y);
}
int main(){
cin>>x>>y;
long long ans=0;
for(long long i=1;i<=sqrt(x*y);i++){
if(x*y%i==0&&gcd(i,x*y/i)==x) ans++;
}
ans*=2;
if(x==y) ans--;
cout<<ans;
return 0;
}
算术基本定理告诉我们:一个合数的可以写成质因数的乘积的形式,并且次分解唯一。
所以我们知道两个数的最大公约数就是质因数分解中相同质数的指数小的那一群的乘积 3^1 * 5^1
同样的,我们可以知道最小公倍数就是质因数分解中每个相同质数质数大的那一群的乘积 2^2 * 3^2 * 5^2
扩展:最大公倍数
解题思路:
-
既然是公倍数要最大,考虑三个互为质数、且数越大越好,那么从N开始往后找
-
如果N为奇数时,那么N-1为偶数,N-2为奇数,最大值必定是n(n-1)(n-2)n(n−1)(n−2)。
-
如果N为偶数时,N要考虑是否能被3整除的情况,如果不能被整除,那么最大值是n(n-1)(n-3)n(n−1)(n−3);反之(n-1)(n-2)(n-3)(n−1)(n−2)(n−3)为最大值。
-
如果输入的数小于等于2,直接返回即可。