数论知识集锦(持续更新中)

本文深入探讨了数论中的线性筛法与扩展欧几里得算法,详细讲解了线性筛法的基本原理及其在积性函数求值中的应用,并通过实例介绍了扩展欧几里得算法求解逆元的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

数论还是比较重要的一部分,需要我们好好掌握
话不多说,直接上算法吧

1.线性筛

这还是比较简单的一部分了吧
线性筛是什么就不多说了,顾名思义即可
先讲讲普通筛
代码如下:

memset(isprime, true, sizeof(isprime));
isprime[1] = false;
for (int i = 2; i * i <= n; i++)
    for (int j = i; i * j <= n; j++)
        isprime[i * j] = false;

代码很简单,时间复杂度约为O(nlog2log2n)
时间还能凑合,但是遇到n=107级别的时候就GG了
所以才要引进线性筛这种神奇的操作……
方法? 每一个数都只被自己的最小质因子筛一次
为什么普通筛时间会爆,因为一个数可能被筛了很多次……
看看线性筛的代码(比较丑陋)

memset(isprime, true, sizeof(isprime));
int len = 0; isprime[1] = false;
for (int i = 2; i <= n; i++) {
    if (isprime[i]) prime[++len] = i;
    for (int j = 1; j <= len && i * prime[j] <= n; j++) {
        isprime[i * prime[j]] = false;
        if (i % prime[j] == 0) break;
    }
}

这就是线性筛的代码了
如果你认为它只能筛素数,那就大错特错了……
在这里我们引入一些概念
积性函数:当(a,b)=1时,满足f(ab)=f(a)f(b),则f为积性函数
完全积性函数:对于任意数对(a,b),都满足f(ab)=f(a)f(b),则f为完全积性函数
常见的积性函数:
欧拉函数φ(n)表示在1n中和n互质的数的个数
约数个数d(n)表示n约数个数
约数和σ(n)表示n的约数和
线性筛可以解决积性函数求值的问题

我们拿欧拉函数φ作为例子
对于一个积性函数,我们需要考虑2种情况
1.n=pkp为质数
2.n=pk11×pk22××pkmm(p1,k1,p2,k2pm,km都为正整数)
当属于第一种情况的时候,很容易得到

φ(n)=pkpk1=n(11p)

当属于第二种情况的时候,可以通过质因数分解得到
φ(n)=φ(pk11)φ(pk22)φ(pkmm)=n(11p1)(11p2)(11pm)

所以线性筛即可

还是看一看具体代码比较好

memset(phi, 0, sizeof(phi));
int len = 0; phi[1] = 1;
for (int i = 2; i <= n; i++) {
    if (!phi[i]) {
        phi[i] = i - 1;
        prime[++len] = i;
    }
    for (int j = 1; j <= len && i * prime[j] <= n; j++) {
        int k = i * prime[j];
        if (i % prime[j] == 0) {
            phi[k] = phi[i] * prime[j];
            break;
        }
        phi[k] = phi[i] * (prime[j] - 1);
    }
}

和线性筛素数的代码大同小异
再举个例子,约数和σ(n)
还是和求φ(n)一样的思路,分类讨论两种情况
1.n=pk
2.n=pk11×pk22××pkmm(p1,k1,p2,k2pm,km都为正整数)
第一种情况的σ(n)很好求
很显然

σ(n)=σ(np)+n=i=0kpi

第二种也差不太多,大同小异
σ(n)=σ(pk11)σ(pk22)σ(pkmm)=i=1mj=0kipji

这个和求φ(n)的过程差不多,就不写代码了

基本所有的积性函数怎么求值都可以这样分两步求
那么,线性筛这个部分就告一段落了

2.扩展欧几里得

欧几里得算法求最大公约数大家应该都会
时间复杂度约为O(log2n)
引入一个定理:
对于一个不定方程ax+by=c,只有(a,b)|c的时候,方程有整数解
推论:若(a,b)=1,则ax+by=1有解(很显然吧)
那么,扩展欧几里得就是一个可以求得这个方程的其中一组整数解的算法
方程为:ax+by=c,c=(a,b)
我们考虑使用欧几里得算法的思想,即为辗转相除法
a=bq+r,r=a mod b,递归求出bx+ry=c的解
假设我们求出bx+ry=c的解为x,y
a=bq+r代入ax+by=c,则可以转化为:

b(xq+y)+rx=c

所以x=xq+y,y=x
所以原始的解x=y,y=xyq
递归求解即可
注意一下边界,当b=0时,x=1,y=0
下面放一放代码吧
void calc(int a, int b, int &x, int &y) {
    if (b==0) {
        x = 1, y = 0;
        return;
    }
    int q = a / b, r = a % b;
    calc(b, r, y, x);
    y -= q * x;
}

代码确实比较简洁,但是写起来可能还是需要一定的思考和理解的
那么,可能你会问,这东西有什么用呢?
好,那我们来看看
来引进一个十分重要的概念,叫逆元

(1)逆元

ax1(modb),则称xa关于模b的逆元
这个定义式其实等价于ax+by=1
只有当(a,b)=1时才会有逆元,否则不存在
我们一般这么写
a的逆元为x1(modb),形象化说,就是a11(modb)
那么,在模b意义下, a1=x 这样可能比较容易理解
求逆元直接用扩展欧几里得即可
显然,在[0,b)的范围内,a关于模b的逆元(如果存在的话)是唯一的
反证法即可
只求一个很容易,那么我们要求1n关于模质数p的逆元呢?
并且n106107级别
我们来推导一下吧
首先,111(modp)(十分明显)
那么,我们假设p=ax+b,b<x,1<x<p
把这个式子带进去,就会得到ax+b0(modp)
两边同时乘上x1b1,可得

ab1+x10(modp)x1ab1(modp)x1pi×(p mod i)1(modp)

所以,逆元就可以推出来了

代码如下

for (inv[1] = 1, i = 2; i <= n; i++)
    inv[i] = (p - p / i) * inv[p % i] % p;

逆元这个东西还是十分重要的,在一些求方案数的dp中会大量使用,特别是遇到除法的时候

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值