关于神奇的约数的基础合集

好啦,之前我们学了数论的一个基础概念:质数,同时也学了一些与质数相关的基本操作的算法实现方法,那么今天我们就来学一下数论的另一个基础概念:约数。
当然,约数的运用和质数还是有许许多多牵连的,所以还不会质数的小伙伴,可以先去康康这个鸭qwq
关于可爱的质数的基础合集
那么接下来,就让我们一起来看看,什么是约数,而它又有一些怎样的神奇操作把!

1.约数的概念

约数的概念应该不会有人忘记吧,和质数一样,也是小学就学过的概念,而且时时刻刻穿插在我们的数学学习生活中,比起质数运用的频率要高了许多,也更不容易忘记,不过为了内容的完整性还是写一下吧QwQ
对于一个整数N,如果有一个整数d可以将N整除,则d就是N的一个约数,也称N的因数。
好了,这就是约数的概念啦。那么接下来我们了解约数,和了解质数一样,从最最简单的操作开始。

2.试除法求约数

想要了解约数,那就得先找到约数。那么对于给定的任意一个数n,我们应该怎么找到它的约数呢?嘿嘿,相信康到标题的小可爱都已经知道了。
没错!!!又是试除法,它又双叒叕来了!!!
思路大家应该都懂的吧,从1到n进行枚举,能与n整除的数就是n的约数。
不过同样的,如果在不优化的情况下,我们这样寻找质数的时间复杂度是O(n)的,我们同样可以用优化查质数的思路把它的时间复杂度优化成O(sqrt(n)),优化的思路我在质数的时候都写过了,这里就直接来康康优化过后的代码吧。

vector<int> found(int n)
{
	vector<int> res;
	for(int i = 1; i <= n / i; i ++)
	{
		if(n % i == 0) res.push_back(i);
		if(n / i != i) res.push_back(n / i); // 这里防止重复加
	}
	return res;
}

这个板子的思路并不难,也就不放题目了,我们直接下一个QwQ

2. 约数的个数

<1>思路模板

随意给定一个数N,我们如何快速的求出约数的个数呢?
当然,我们用上面的那个方法是一定可以求出来的。但是,我们只用求出约数的个数耶,好似不用一个一个来算呀,那么有没有什么好的办法,比如有没有啥公式可以一步算出来呢?
答案是。。。当然有的!
我们已经知道了,对于任意一个数N,我们可以写成N = p1 ^ a1 * p2 ^ a2 * …
. * pk ^ ak 的形式,那么,N的约数个数T = (a1 + 1) * (a2 + 1) * … * (ak + 1)
那么这个公式是怎么来的呢?我们假设N有一个约数W, 那么对于W,我们一定有W = p1 ^ b1 * p2 ^ b2 * … * pk ^ bk,对于bi,它的取值范围一定是0 <= bi <= ai。
这就变成了排列组合的问题,对于每一个质数pi,它的指数都有0到ai这(ai + 1)种选择,所以这个数总的约数格式也就是每个质数的指数的选择个数的乘积,也就是上面的公式。
我们来一起康康代码的实现吧QwQ

int num(int n)
{
	unordered_map<int, int> hash;
	for(int i = 2; i <= n; i ++)
	{
		while(n % i == 0)
		{
			hash[i] ++;
			n /= i;
		}
	}
	if(n > 1) hash[n] ++;
	
	int res = 1;
	for(auto x : hash)
	{
		res = res * (x.second + 1);
	}
	return res;
}

这道题的思路其实还是有一点点复杂,所以我们来一道题目康一下吧。

<2>例题

约数个数
给定n个正整数aiai,请你输出这些数的乘积的约数个数,答案对109+7109+7取模。
输入格式
第一行包含整数n。
接下来n行,每行包含一个整数aiai。
输出格式
输出一个整数,表示所给正整数的乘积的约数个数,答案需对109+7109+7取模。
数据范围
1≤n≤1001≤n≤100,
1≤ai≤2∗1091≤ai≤2∗109
输入样例:
3
2
6
8

输出样例:
12

AC代码

#include
using namespace std;

const int mod = 1e9 + 7;

int main()
{
    int T, n;
    cin >> T;
    unordered_map<int, int> hash;
    while(T --)
    {
        cin >> n;
         for(int i = 2; i <= n / i; i ++)
        {
            while(n % i == 0)
            {
                n /= i;
                hash[i] ++;
            }
        }
        if(n > 1) hash[n] ++;
    }
    long long ans = 1;
    for(auto t : hash)
    {
        ans = ans * (t.second + 1) % mod;
    }
    cout << ans << endl;
    return 0;
}

3.约数的和

<1>思路模板

和约数的个数一样,约数的和同样也有自己的公式。
我们假设一个数N,它的约数的和为sum,我们可以得到公式sum = (p1 ^ 0 + p1 ^ 1 + … + p1 ^ a1) * (p2 ^ 0 + p2 ^ 1 + … + p2 ^ a2) … (pk ^ 0 + pk ^ 1 + … + pk ^ ak) 那么这个公式是怎么得来的呢?在约数的个数中,我们已经提到过了,N的任意一个约数W,都可以写成W = p1 ^ b1 p2 ^ b2 * … * pk ^ bk的形式,而且每个bi的取值范围都是0到ai的,所以当把所有约数相加时,每种指数搭配的情况都会不重复的出现一次,其实也就是我们给出的公式的展开式。
我们来一起康康模板

int sum(int n)
{
	unordered_map<int, int> hash;
	for(int i = 2; i <= n / i; i ++)
	{
		while(n % i == 0)
		{
			hash[i] ++;
			n /= i;
		}
	}
	if(n > 1) hash[n] ++;
	int res = 1;
	for(auto x : hash)
	{
		int p = x.first, a = x.second;
		int temp = 1;
		while(a --) temp = temp * p + 1;
		res = res * temp;
	}
	return res;
}

同样的,看完模板,我们一起看道题目吧

<2> 例题

约数的和
给定n个正整数aiai,请你输出这些数的乘积的约数之和,答案对109+7109+7取模。
输入格式
第一行包含整数n。
接下来n行,每行包含一个整数aiai。
输出格式
输出一个整数,表示所给正整数的乘积的约数之和,答案需对109+7109+7取模。
数据范围
1≤n≤1001≤n≤100,
1≤ai≤2∗1091≤ai≤2∗109
输入样例:
3
2
6
8

输出样例:
252

AC代码

#include
using namespace std;

const int mod = 1e9 + 7;

int main()
{
    int T, n;
    cin >> T;
    unordered_map<int, int> hash;
    while(T --)
    {
        cin >> n;
         for(int i = 2; i <= n / i; i ++)
        {
            while(n % i == 0)
            {
                hash[i] ++;
                n /= i;
            }
        }
        if(n > 1) hash[n] ++;
    }
     long long ans = 1;
     for(auto t : hash)
    {
        int p = t.first, a = t.second;
        long long res = 1;
        while(a --)  res = (res * p + 1) % mod;
        ans = ans * res % mod;
    }
    cout << ans << endl;
    return 0;
}

4. 最大公约数

<1> 最大公约数的概念

好啦,这是我们约数的最后一部分内容了,最大公约数,相信许多小伙伴也是早就已经知道这样一个概念了,但是我们在这里还是稍稍提一下下。给定两个任意的整数a和b,如果他们能同时被c整除,则称c为a和b的公约数,若c是最大的能同时整除a,b的数,则我们称c为a,b的最大公约数。
所以接下来,我们要解决掉问题,也就是怎么快速的找到两个数的最大公约数。

<2>欧几里得算法

欧几里得算法,其实在我们的高中数学课本中已经出现过了。不过它的名字更加形象粗暴一点:辗转相除法。
嘿嘿,欧几里得算法你可能不认识,但是说辗转相除法,应该大多数小可爱是有影响的吧。
欧几里得算法的实现基础是:对于任意整数a和b,我们都可以得到a和b的最大公约数,与b和a%b的最大公约数相等。
那么,我们怎么退出这个规律的呢?我们首先得了解一些特殊的性质。我们可以很容易的想通,如果you 一个数d,满足d | a 并且d | b,则可以得出d | x * a + y * b。而a%b的值,我们可以通过化简得到a%b = a - a / b(向下取整) * b,我们把 - a / b 看成b的系数,结合上面的性质,就可以得出如果d | a 且 d | b,则d | a % b,我们取出d的可能值中的最大值,也就是a和b的最大公约数。
算法的模板也非常的简单,一行代码就搞定了。

int gcd(int a, int b)
{
	return b ? gcd(b, a % b): a;
}

如果实在很不理解原理,直接记住代码其实也蛮好,毕竟简单过头了,这里也就不给例题了QwQ

好啦,这就是数论约数的部分基础内容了,和质数一样,在更深入的问题中会有不同的应用,如果碰到有趣的,我还是会单独重新整理。

你可能感兴趣的:(关于神奇的约数的基础合集)