AcWing第四章算法模板总结——数学知识

本文介绍了质数的判定与分解质因数的算法,包括试除法和筛法,并详细讲解了约数的计算方法。此外,还探讨了欧拉函数的定义、计算公式及其应用,以及快速幂在求模运算中的高效性。

一、质数
二、约数
三、欧拉函数
四、快速幂
五、扩展欧几里得算法
六、中国剩余定理
七、高斯消元
八、组合计数
九、容斥原理
十、简单博弈论

一、质数

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

(一)质数的判定——试除法

1、sqrt(x)
时间复杂度O(n½)
sqrt(x)这个函数执行的比较慢,所以不推荐

bool is_prime(int x) 
{
   
   
    // 1、质数大于1
    if(x < 2) return false;
    for(int i = 2; i <= sqrt(x); i++) 
    {
   
   
        if(x % i == 0) // 2、只包含1和本身这两个约数
        	return false;	
     }
    return true; 
}

2、推荐此方法i ≤ n / i
核心:i <= x / i可以有效提高性能

bool is_prime(int x) 
{
   
   
    // 质数大于1
    if(x < 2) return false;
    for(int i = 2; i <= x / i; i++) 
    {
   
   
        if(x % i == 0) 
        	return false;
    }
    return true;
}

(二)分解质因数——试除法

时间复杂度O(n½)
思想:从小到大尝试n的所有因数

解释代码里面x % i == 0的i一定是质数,这里可以怎么理解:i = c,如果c是合数且满足x % c == 0,那么一定存在比它小的另外两个数a,b,满足c = a * b。但是由于i是从2开始递增的,所以早在i递增到a或者b(这两个比c小的数的时候),x就已经被整除了,所以根本到不了i = c这个地方。来个实际的例子,x = 16,x就会在i = 2的时候被整除完,根本不会到所谓的i = 4。也就是说到i = c这个地方的时候x已经不存在2~i-1的任何质因子了
代码:

//试除法分解质因数 
void divide(int x)
{
   
   
    for (int i = 2; i <= x / i; i ++ )
	{
   
   
        if (x % i == 0)	// i一定是质数,具体解释看上面蓝色字体
        {
   
   
            int s = 0;	// s代表x可以被i整除的次数
            while (x % i == 0) x /= i, s ++ ;
            printf("%d %d\n", i, s);	// 这里可以使用vector<int, int> res;来记录
        }
	}
    if (x > 1) printf("%d %d\n", x, 1);	// 输出那个大于根号x的最大质因子
}

(三)筛法求素数

1、朴素筛法求素数

算法核心:把每个质数的所有倍数筛掉,所有剩下的数就是质数

调和级数:1 + 1/2 + 1/3 +…+ 1/n = lnn + c(c欧拉常数=0.577)

算法时间复杂度:内部循环总次数是n/2,n/3,n/4…1,累加得n(1/2 + 1/3 + 1/4 +…+1/n)=nlnn=nlogn
模板代码:

const int N = 1000010;

int primes[N], cnt;	// primes[]记录素数,cnt记录素数的个数,作为primes[]的索引
bool st[N];			// st[x]存储x是否被筛掉 true 说明被筛过

void get_primes(int n) 
{
   
   
    for (int i = 2; i <= n; i ++ )
    {
   
   
        if (!st[i])  //i如果没被筛过,说明i是质数,将i加入质数数组中
			primes[cnt ++ ] = i;
        
        //接下来对该质数的所有倍数进行筛选
        for (int j = i + i; j <= n; j += i)
            st[j] = true;
    }
}

缺点:同一个合数可能会被重复筛,比如8,会被2,4同时筛一次

2、埃式筛法O(nloglogn)

算法核心:把每个质数的所有倍数筛掉

质数定理:1~n中有n/logn个质数

实际算法时间复杂度:当数据不是足够大时与O(n)接近
代码:

const int N = 1000010;

int primes[N], cnt;	// primes[]记录素数,cnt记录素数的个数,作为primes[]的索引
bool st[N];			// st[x]存储x是否被筛掉 true 说明被筛过

void get_primes(int n) 
{
   
   
    for (int i = 2; i <= n; i ++ )
    {
   
   
        if (!st[i]) //st如果是true 说明被筛过,那么它的倍数肯定被筛过,所以直接跳过
        {
   
   
        	primes[cnt ++ ] = i;
	        for (int j = i + i; j <= n; j += i)	//接下来对该质数的所有倍数进行筛选
	            st[j] = true;
        }
    }
} 

3、线性筛法——每次用这个!

思路:把每一个合数用他的质因子筛掉就可以了,x只会被它的最小质因数筛去,即每个数字只被筛选一次,因此是线性的n。
算法时间复杂杂度:因为每个数只被它的最小质因子筛过一次,因此为O(n)

int primes[N], cnt;	// primes[]记录素数,cnt记录素数的个数,作为primes[]的索引
bool st[N];			// st[x]存储x是否被筛掉 true 说明被筛过

void get_prime(int x)
{
   
   
    for(int i = 2 ; i <= x ; i++)
    {
   
   
        if(!st[i]) primes[cnt ++] = i;
        // 现在是从小到大筛掉所有合数,每个合数只会被它的最小质因子筛一次。所以时间复杂度是O(n)
        for(int j = 0 ; primes[j] <= x / i ; j++)
        {
   
   										
            st[primes[j] * i] = true; //筛去primes[j]的倍数   
             /*
			 针对st[primes[j] * i] = true;本质上分两种情况
			 1.i % primes[j] == 0, 因为primes[j]是顺序遍历,因此当第一次模为零时,primes[j]一定为i的最小质因子,
			 primes[j]也一定为primes[j] * i的最小质因子
			 
			 2.i % primes[j] != 0, 同样因为primes[j]是顺序遍历,primes[j]一定小于i的所有质因子所以primes[j]也
			 一定为primes[j] * i最小质因子
			 */
            if(i % primes[j] == 0) break;	// 这句话成立的时候说明,primes[j]一定是i的最小质因子
        }
        
    }
} 
这里以9为例子,当i取到3的时候,并且primes[j]取到3的时候,此时就会被筛掉,而且这个primes[j]=39的最小质因子
以24为例子,当i取到12的时候,并且primes[j]取到2的时候,此时就会被筛掉,而且这个primes[j]=29的最小质因子
x=24的时候,筛掉数的顺序是
4___i = 2 primes[] = 2
6___i = 3 primes[] = 2
9___i = 3 primes[] = 3
8___i = 4 primes[] = 2
10__i = 5 primes[] = 2
15__i = 5 primes[] = 2
12__i = 6 primes[] = 2
14__i = 7 primes[] = 2
21__i = 7 primes[] = 3
16__i = 8 primes[] = 2
18__i = 9 primes[] = 2
20__i = 10 primes[] = 2
22__i = 11 primes[] = 2
24__i = 12 primes[] = 2

4、把数x分解成质因数的乘积形式

eg:360 = 23 * 35 * 51
代码:

// 返回的是map,prime[a]=b 的含义是a的b次幂 
unordered_map<int , int> fenjie(int x)
{
   
   
	unordered_map<int , int> primes; //利用哈希表存储所有的指数和底数,prime[a]=b 的含义是a的b次幂 
    for(int i = 2 ; i <= x / i; i++)	// 求x的质因数
    {
   
   
        while(x % i == 0)
        {
   
   
        	x /= i;
        	primes[i] ++;	// x对应的这个质因数i,i的指数加一
        }
    }
    if(x > 1) primes[x] ++;	// 如果x大于1,说明现在的x是原始x的比较大的一个质因数

	return primes;
}
tips:
这里的primes可以使用(auto t : primes)迭代,然后用t.first,t.second来访问

二、约数

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

(1)试除法求所有约数O(n½)

思路:从小到大枚举较小的约数即可

// 求x的所有约数
vector<int> get_divisors(int x)
{
   
   
    vector<int> res;
    
    for(int i = 1 ; i <= x / i ; i++) 
    {
   
   
        if(x % i == 0) 	// i是x的一个约数,且x / i 也是x的一个约数——i * (x / i) = x
        {
   
   
            res.push_back(i);
            if(i != x / i) res.push_back(x / i); // 避免i和x / i 相同,重复加入
        }
    }
    
    sort(res.begin() , res.end());
    
    return res;
}

(2)求某个数的约数个数

在这里插入图片描述

算法思想:
基于算数基本定理:
N = p1α1 * p2α2 * … * pkαk
N的任意一项约数d可以写成 d = p1β1+p2β2+…+pkβk (这里β是满足在0—α的)

不同的β数组,组合而成的约数d是不同(即因式分解不同)
同理不同的a数组组合而成的N也是不同的,

而α1有α1+1种选法,α2有α2+1种选法…αk有αk+1种选法
因此约数个数:(α1+1)(α1+1)(α3+1)…(αk+1)

如果看不懂这里的证明,可以去看b站这个视频,链接如下公式推导:约数个数和约数之和

题目描述
给定n个正整数ai,请你输出这些数的乘积的约数个数,答案对1e9+7取模。
代码:

#include <bits/stdc++.h>
using namespace std
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值