数论 - 质数和约数

前言

本章博客将介绍质数和约数的常用模板,这些题目都比较简单,都可以通过暴力获取答案,但是时间复杂度较高,不符合算法比赛的要求,所以本篇博客介绍的方法都是时间复杂度低效率高的方法,并给出简化复杂度的思路。

质数部分将介绍:质数的判定,分解质因子,筛选质数
约数部分将介绍:求约数,约数的个数,约数之和,最大公约数

一、质数

在大于1的整数中,如果只包含1和本身这两个约数,就被称为质数,或者素数

1.质数的判定-试除法

  • 约数成对出现,如果d|n,则n/d必然也整除n,所以我们枚举的时候只需要枚举前一部分即可,也就是d<=r/d这一部分
  • 不写成d*d<=r是因为防止爆int
  • 不写成d<=sqrt( r )是因为sqrt效率低
  • 时间复杂度从O(n)降低到了O(sqrt(n))
bool is_prime(int n)
{
	if(n < 2) return false;
	for(int i = 2; i <= n / i; i ++ )
		if(n % i == 0)
			return false;
	return true;
}

2.分解质因子-试除法

  • N = P1a1 * P2a2 * P3a3 * …… * Pkak(P1到k均为质数)
  • 从小到大依次枚举,这个不需要考虑是否是质数,因为当枚举到i且成立时,就已经说明i不能被2~i-1整除,说明i已经是质数
  • n最多只有一个大于sqrt(n)的数,因为如果有两个大于,则两数相乘大于n不符合逻辑,所以枚举只需要枚举到i<=n/i,最后剩余的一个不是1就是大于sqrt(n)的最后一个质因子
  • 时间复杂度从O(n)降低到了介于O(logn)~O(sqrt(n))
void divide(int n)
{
	for(int i = 2; i <= n / i; i ++ )
		if(n % i == 0)
		{
			int s = 0; //次数 
			while(n % i == 0)
			{
				n /= i;
				s ++;
			}
			 cout << i << ' ' << s << endl;
		}
	
	if(n > 1) cout << n << ' ' << 1 << endl;
	puts(""); //换行 
}

3.筛选质数

  • 埃氏筛法
  • 从小到大枚举质数(不用枚举所有数),每次删除质数的倍数
  • 时间复杂度从O(nlogn)降低为O(nloglogn)
const int N = 1000010;

int primes[N], cnt;
bool st[N]; //判断是否是合数

void get_primes(int n)
{
	for(int i = 2; i <= n; i ++ )
	{
		if(!st[i])
		{
			primes[cnt++] = i;
			for(int j = i + i; j <= n; j += i) st[j] = true;
		}
	}
}
  • 线性筛法
  • i%pj == 0:由于从小向大枚举,pj一定是i的最小质因子,pj一定是pj * i的最小质因子
  • i%pj != 0:由于从小向大枚举,pj一定小于i的所有质因子,pj也一定是pj * i的最小质因子
  • 所以在i%pj == 0时break即可,后面的都不满足了,但是从开始枚举到此的全都满足pj一定是pj * i的最小质因子
  • 时间复杂度比埃氏更低
const int N = 1000010;

int primes[N], cnt;
bool st[N];

void get_primes(int n)
{
	for(int i = 2; i <= n; i ++ )
	{
		if(!st[i]) primes[cnt ++ ] = i;
		for(int j = 0; primes[j] <= n / i; j ++ )
		{
			st[primes[j] * i] = true;
			if(i % prime[j] == 0) break;
		}
	}
}

二、约数

约数,又称因数。整数a除以整数b(b≠0) 除得的商正好是整数而没有余数,就说a能被b整除,或b能整除a。a称为b的倍数,b称为a的约数。

1.求约数-试除法

  • 约数成对出现,如果d|n,则n/d必然也整除n,所以我们枚举的时候只需要枚举前一部分即可,也就是d<=r/d这一部分
vector<int> get_divisors(int n)
{
	vector<int> res; //存n的所有约数
	
	for(int i = 1; i <= n / i; i ++ )
	{
		res.push_back(i);
		if(i != n / i) res.push_back(n / i);
	}
	
	sort(res.begin(), res.end());
	return res;
}

2.约数的个数

  • N = P1a1 * P2a2 * P3a3 * …… * Pkak
  • 约数个数 = (a1+1)*(a2+1)*(a3+1)*……*(ak+1)
  • 运用前面分解质因子-试除法
/*题目:给定 n 个正整数 ai,请你输出这些数的乘积的约数个数,答案对 109+7 取模。*/
#include <iostream>
#include <algorithm>
#include <unordered_map>
#include <vector>

using namespace std;

typedef long long LL;

const int N = 110, mod = 1e9 + 7;

int main()
{
    int n;
    cin >> n;

    unordered_map<int, int> primes;

    while (n -- )
    {
        int x;
        cin >> x;
		
		//分解质因子-试除法
        for (int i = 2; i <= x / i; i ++ )
            while (x % i == 0)
            {
                x /= i;
                primes[i] ++ ;
            }
        if (x > 1) primes[x] ++ ;
    }

    LL res = 1;
    for (auto p : primes) res = res * (p.second + 1) % mod;

    cout << res << endl;

    return 0;
}

3.约数之和

  • N = P1a1 * P2a2 * P3a3 * …… * Pkak
  • 约数之和=(P10+P11+……+P1a1)*……*(Pk0+Pk1+……+Pkak)
  • 运用前面分解质因子-试除法
/*题目:给定 n 个正整数 ai,请你输出这些数的乘积的约数之和,答案对 109+7 取模。*/
#include <iostream>
#include <algorithm>
#include <unordered_map>
#include <vector>

using namespace std;

typedef long long LL;

const int N = 110, mod = 1e9 + 7;

int main()
{
    int n;
    cin >> n;

    unordered_map<int, int> primes;

    while (n -- )
    {
        int x;
        cin >> x;
		
		//分解质因子-试除法
        for (int i = 2; i <= x / i; i ++ )
            while (x % i == 0)
            {
                x /= i;
                primes[i] ++ ;
            }
        if (x > 1) primes[x] ++ ;
    }

    LL res = 1;
    for (auto p : primes)
    {
        LL a = p.first, b = p.second;
        LL t = 1;
        while (b -- ) t = (t * a + 1) % mod; //求公式的每个括号项的技巧
        res = res * t % mod;
    }

    cout << res << endl;

    return 0;
}

4.最大公约数-欧几里得算法

  • 核心:辗转相除
  • d|a且d|b可以推得:d|a*x+b*y
  • 由上式可得:(a, b)最大公约数 =(b, a mod b)
int gcd(int a, int b)
{
	return b ? gcd(b, a % b) : a;
}
  • 24
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值