因数
因数
定义
在整数范围内,若 \(a\) 为整数, \(b\) 为非零整数,若存在整数 \(k\),使得 \(a=kb\),则称 \(b\) 是 \(a\) 的因数,记作 \(b\space | \space a\).
求所有因数
思路:
试除法
枚举每个可能的因数,进行试除。由于一个数的因数也是成对出现的,即如果 \(d|n\) ,则 \(\frac{n}{d}|n\) . 因此,只需要枚举 \([1,\lfloor \sqrt{n} \rfloor )\) 之间的整数即可。
时间复杂度:
\(O(\sqrt{n})\)
代码:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
vector<int> get_dividors(int n) { //求n的所有因数,保存至res向量中
vector<int> res;
for (int i = 1; i <= n / i; i++)
if (n % i == 0) {
res.push_back(i);
if (i != n / i) //完全平方数,不能重复添加
res.push_back(n / i);
}
sort(res.begin(), res.end()); //排序,时间复杂度小于sqrt(n)
return res;
}
int main() {
int x;
cin >> x;
auto res = get_dividors(x);
for (auto t : res) cout << t << " ";
return 0;
}
分解质因数
思路:
本质上和求因数的方法一样:试除法。每次都用可以整除的因数除到不能再除为止,再枚举下一个数。最后作为除数的都是质数。
代码:
#include<iostream>
using namespace std;
int main() {
int n, i = 2;
cin >> n;
while (n != 1) {
int t = 0;
while (n % i == 0) { //满足这个条件的i一定是质数
n /= i;
t++; //记录质因数i出现的次数
}
if (t == 1)
printf("%d", i);
if (t > 1)
printf("%d^%d", i, t);
if (t > 0 && n != 1)
printf("*");
i++;
}
return 0;
}
求因数个数
思路:
根据算术基本定理(唯一分解定理),任何一个大于1的自然数 \(N\),如果 \(N\) 不为质数,那么 \(N\) 可以唯一分解成有限个质数的乘积 \(N=P_1^{\alpha_1}P_2^{\alpha_2}P_3^{\alpha_3} \cdots P_k^{\alpha_k}\) ,这里 \(P_1<P_2<P_3< \cdots <P_k\) 均为质数,其中指数 \(\alpha_i\) 是正整数。
\(N\) 的任何一个因数 \(d\) 的形式为:
相当于在N的质因数里面选,故总因数个数为:
举个例子:
\(18 = 2^1*3^2\),那么18的因数个数为 \((1+1)*(2+1)=6\) 个。显然18的因数有:\(1,2,3,6,9,18\),确实为6个。
小tip:
int 范围内的数字的因数个数最大为1536个。
时间复杂度:
\(O(\sqrt n)\)
代码:
#include <iostream>
using namespace std;
int cnt_dividors(int n) {
int res = 1;
int p = 2; //枚举每个数
while (n != 1) {
int a = 0; //质因数个数
while (n % p == 0) { //成立时p一定为质数
n /= p;
a++;
}
res *= (a + 1);
p++;
}
return res;
}
int main() {
int x;
cin >> x;
auto cnt = cnt_dividors(x);
cout << cnt;
return 0;
}
求因数之和
思路:
公式为:
即:
将此公式展开可得到一个总共 \(cnt = (\alpha_1+1)(\alpha_2+1)\cdots(\alpha_k+1)\) 项的多项式,其中每项的形式为 \(P_1^{\beta_1}P_2^{\beta_2}P_3^{\beta_3}\cdots P_n^{\beta_k},其中0\le\beta_i\le\alpha_i\) ,恰好符合上一章节所讲内容。故这里的 \(sum\) 为所求的所有因数之和。
举个例子:
\(18 = 2^1 \times 3^2\),那么18的因数之和为 \((2^0+2^1)*(3^0+3^1+3^2)=39\) 个。显然18的因数之和:\(1+2+3+6+9+18=39\),确实为39。
关键代码分析:
1.
对于求 \(P_1^0+P_1^1+\cdots +P_1^{\alpha_1}\) 这样一个式子,采用下面这种方法求解:
LL t = 1;
while (a--) t = t * p + 1; //p为质因数,a为指数
第一次:\(t = p + 1\)
第二次:\(t=(p+1)\times p+1=p^2+p+1\)
第三次:\(t=(p^2+p+1)\times p+1=p^3+p^2+p+1\)
第\(\alpha_1\)次:\(t=p_1^0+p_1^1+\cdots +p_1^{\alpha_1}\)
如此来看,这样计算的时间复杂度为 \(O(\alpha_1)\)。
2.
不建议采用快速幂求解,理由如下:
对于 \(P_1^{\alpha_1}\) 来说,快速幂求解的时间复杂度为 \(O(log\alpha_1)\),那么这整个式子的时间复杂度就为 \(O(\alpha_1log\alpha_1)\),效率更低了。
3.
可以选择采用快速幂+等比数列求和公式来求解:
这样的话时间复杂度为 \(O(log\alpha_1)\)。
时间复杂度:
采用 1 的话,总计算量为 \(k\times \alpha\),其中 \(k\) 为自然数 \(N\) 的质因数个数。
代码:
LL sum_dividors(int n) {
LL res = 1;
int p = 2; //枚举每个数
while (n != 1) {
LL t = 1;
while (n % p == 0) { //成立时p一定为质数
n /= p;
t = t * p + 1;
}
res *= t;
p++;
}
return res;
}
最大公因数
欧几里得算法(辗转相除法)
公式:
用gcd(a,b)表示a与b的最大公因数,则:
当 \(b=0\) 时,\(gcd(a,0)=a\).
举个例子:
当我们求 36 和 24 的最大公因数时:
证明:
设 \(a=bk+c\),显然有 \(c=a\space mod \space b\).
1.设 \(d\space |\space a,d\space |\space b\),则 \(c=a-bk\) 可得出:
因此 \(d \space | \space c\).
故:若 \(d\) 为 \(a\) 和 \(b\) 的公因数,则 \(d\) 也是 \(b\) 和 \(a\space mod\space b\) 的公因数。
2.反过来:
设 \(d\space |\space b,d\space |\space (a\space mod\space b)\),则同理可得到:
因此 \(d \space | \space a\).
故:若 \(d\) 为 \(b\) 和 \(a\space mod\space b\) 的公因数,则 \(d\) 也是 \(a\) 和 \(b\) 的因数。
综上所述,\(\space a\space和\space b\space的公因数\Leftrightarrow b \space和\space a\space mod\space b的公因数\),即 \(gcd(a,b) = gcd(b,a\space mod\space b)\).
另:
我们也可以得到以下结论:
求两个数 a 和 b 的所有公因数,即求他们最大公因数的所有因数。
代码:
1.递归写法
int gcd(int a, int b) {
return b ? gcd(b, a % b) : a;
}
2.循环写法
int gcd(int a, int b) {
while (b) {
int temp = b;
b = a % b;
a = temp;
}
return a;
}
3.多个数字求最大公因数
int mul_gcd(vector<int> vec) {
int res = vec[0];
for (auto t : vec) res = gcd(res, t);
return res;
}

浙公网安备 33010602011771号