算法中的数学:约数
1.求一个整数的所有约数
对于一个整数x,他的其中一个约数若为i,那么x/i也是x的一个约数。而其中一个约数的大小一定小于等于根号x(完全平方数则两个约数都为根号x),所以我们只需要遍历到根号x,然后计算出另一个约数即可
代码实现:
int a[N]; int cnt; void getnum(int x) {for(int i = 1; i <= x/i; i++){if(x%i == 0){a[++cnt] = i;if(x/i != i){ a[++cnt] = x/i;}}} }
时间复杂度为O(根号n)
2.求(1~n)的每个数的约数集合
如果我们对每个数都使用试除法会导致算法时间复杂度过高,为O(n*根号n)
所以我们使用正难则反的思想,遍历1~n的所有数,然后将它作为约数给到所有他的倍数。
图示:
这里我们演示了如何使用该方法将每个数的约数求出来。
这样子时间复杂度就来到了nlogn
代码实现:
int n; vector<int> a[N]; void func() { for(int i = 1; i <= n; i++){for(int j = 1; i*j <= n; j++){a[i*j].push_back(i);}} }
3.约数个数定理
根据唯一分解定理我们可知:一个数可以被拆分成多个质数的任意次方相乘
而这些不同的质数经过组合就可以得到num的约数
图示:
而总结出来的公式就是:
(次方加1)*(次方加1) *.......
补充:
试除法求单个数的约数个数方法一:遍历1~根号n的数将cnt返回
方法二:分解质因数后套用公式计算
4.约数和定理
计算方法:将每个质因数的所有分别种类相加,记为sum,然后不同的质因数的sum乘起来
右边我们就是在计算约数之和的具体过程
5.例题讲解
审题:
本题需要我们求出一到n的数的所有约数的个数之和思路:
方法一:暴力解法我们可以用试除法计算1到n每个数的所有约数,然后将cnt累加起来,外层循环为遍历1~n,内层为试除法,时间复杂度为O(n根号n)
运行次数为1e12,一定超时
方法二:正难则反
我们可以遍历1~n,不过这里的i含义是约数,用n/i可以求出当前约数一共出现的次数,然后就累加起来。但是这样就要运行n次,也就是1e8次,还是有可能超时
优化:由于当i小于等于n/2的时候,约数出现次数大于等于1,而i大于n/2的时候,约数次数一定为1,所以我们只用遍历到n/2即可,后面的次数都为1,所以后面的约数的出现次数等于后面的约数个数(n-n/2)
解题:
#include<iostream> using namespace std; typedef long long ll; ll n; ll cnt; int main() {cin >> n;for(int i = 1; i <= n/2; i++){cnt += n/i;}cnt += n-n/2;cout << cnt << endl;return 0; }