数论入门————新区qp?(雾

数论入门

快速幂

一个最基本的,在数论题中较为常用的算法:

用于处理乘方后数字较大,以至于要使用模数在中途取模的情况

一般有两种写法:

法一:递归

int qp(int 底数,int 指数,int 模数){
	if(指数==0)return 1;
	if(指数==1)return 底数%模数;//两个特判
	int t=qp(底数,指数/2,模数)%模数;//指数分成两半处理
	return (((t*t)%模数)*(
        指数%2?底数%模数:1//如果指数为奇数,递归后要再乘上一个底数
    ))%模数;//要养成遍地取模的好习惯
}

法二:位运算

int qpow(int 底数,int 指数,int mod) {
    int res=1;//因为是乘法运算,所以初始化结果为1
    while(指数>0){
        //如果当前指数的最低位是1,则将当前基数乘到结果中
        if(指数&1)res=(底数*res)%mod;
        // 基数平方,指数右移一位
        底数=(底数*底数)%mod;//要养成遍地取模的好习惯
        指数>>=1;
    }
    return res;
}

同学:你怎么用中文写代码啊?

我:C++20早就支持中文了!

同学:啊?!(目瞪口呆)

质数

定义很简单,这里就不再赘述了。

我们重要的是讲如何高效率的筛出指数。

法一:试除

最简单、朴素的算法,从 2\sqrt{n} 逐一枚举是否能整除 n 来判断 n 是否为质数

int cnt=0;int prime[114514];//114514 is a funny number
bool check(int x){
    if(x==1) return false;
    for(int i=2;i*i<=x;i++){//从2遍历到sqrt(n)
        if(x%i==0)return false;
    }
    return true;
}
int kkksc03(int a){
    for(int i=1;i<=a;i++){
        if(check(i)){
            prime[++cnt]=i;
        }
    }
}

时间复杂度 O(n\sqrt{n}) ​ 。

空间复杂度 O(n) ​ 。

法二:埃氏筛

2 开始遍历,如果一个数没被标记过,那么这个数就是质数,并在范围内把这个质数的所有倍数都标记一遍

注: 1 不是质数,所以要提前标记,或直接从 2 开始遍历

bool vis[114514];
int cnt=0;int prime[114514];//114514 is a funny number 
void chen_zhe(int a){
    for(int i=2;i<=a;i++){//从2开始遍历
        if(!vis[i]){
             prime[++cnt]=i;
             for(int j=2;j*i<=a;j++){//把倍数标记一遍
             	vis[j*i]=1;
        	}
        }
    }
}

时间复杂度 O(n\ln\ln n)

空间复杂度 O(2n)

法三:欧拉筛

在埃氏筛的基础上,保证每个数只被自己除以自己最小的除 1 外的因子标记一遍。

bool vis[114514];
int cnt=0;int prime[114514];//114514 is a funny number 
void fu_su(int a){
    for(int i=2;i<=a;i++){
        if(!vis[i])prime[++cnt]=i;
        for(int j=1;prime[j]*i<=a;j++){
            if(!prime[j])break;
            vis[prime[j]*i]=1;
            if(i%prime[j]==0)break;
        }
    }
}

时间复杂度 O(n) ​ 。

空间复杂度 O(2n) ​ 。

同学:你为什么数组都要开114514啊?

我:还不是跟xiaoxiaoxia学的。

GCD与LCM

GCD:最大公约数

LCM:最小公倍数

一些常用性质:

  1. \gcd(\gcd(a,b),c)=\gcd(a,\gcd(b,c))

  2. 维护一个集合的 \gcd 时,每增加一个新的元素 \gcd 要么不变,要么减少(最多减少 log_2 首项 次)。

  3. 通过上面这个性质,我们知道一个序列的 \gcd 是单调不增的。

设\ a\ 的质因数分解为:\displaystyle\prod{p_i}^{x_i}\\ b\ 的质因数分解为:\displaystyle\prod{p_i}^{y_i}\\ 则\ \gcd(a,b)=\displaystyle\prod{p_i}^{\min(x_i,y_i)}

-------------------------分----------------割---------------线--------------------------

那么怎么快速求他们呢?

这里就不卖关子了:辗转相除法

\gcd(a,b)=\gcd(b,a\mod b)

这样我们就可以用 O(\log \max(a,b)) 求他们的 \gcd 了!

int gcd(int a,int b){
    return b?gcd(b,a%b):a;//当b=0时,递归到边界,gcd就是a
}

具体怎么证明,我也不知道了…

证明就留给大家自己去学习吧,有答案可以发在讨论区哦!

其实作者自己没看懂证明,快去补啊qwq(逃

那么 lcm 怎么求呢?

不难发现 a,b 的最小公倍数就是他们所有的质因子相乘,再除去两者间重复的质因子,而他们的重复的质因子,就是 \gcd(a,b)

于是我们就可以列出公式 :

lcm(a,b)=\frac{a\times b}{\gcd(a,b)}

也就是说,我们在知道 \gcd(a,b) 的情况下,可以 O(1) 求出 lcm(a,b)

int lcm(int a,int b){
    return a*b/gcd(a,b);//没错,这两部分代码就是这么短小精悍!
}

还是那句话,LATEX炸了的刷新一下
拜托了qwq
不要老是在评论区发看不见LATEX

猜你想搜:

模运算(含辗转相除法证明)

组合数学

莫比乌斯反演

update:\ \ 2024/7/28\ \ \ 16:14 加了一些 \gcd 常用性质。

update:\ \ 2024/7/28\ \ \ 16:56 加了辗转相除法的证明链接。

2 个赞

我的狄利克雷卷积呢?

不是给你放上去了么?

还是那句话,LATEX炸了的刷新一下
拜托了qwq
不要老是在评论区发看不见LATEX

好久不见

O我个sb我还以为那是oiwiki