数论基础

数论:研究数与数之间的关系。如带余除法、因数倍数、同余整除等都是数论。

【筛法】

主要使用的筛法有两个:埃氏筛和欧拉筛(线性筛)。

使用筛法的原因很简单:对于一个数,我们要找它的因数不容易;但是对于一个因数,我们找它的倍数很简单。

【埃氏筛】

因为一个数的倍数一定不是质数,且任何数都能分解为若干质数之积,所以我们每找到一个质数,就把这个质数的所有倍数都标记为合数。

bool p[N] = {}; //p[i]=true表示i为质数
void shai() {
    p[0] = p[1] = false;
    for (int i = 2; i <= N; i++)
        p[i] = true;
    for (int i = 2; i <= N; i++)
        if (p[i]) {
            for (long long j = i; i * j <= N; j++) //删去所有倍数
                p[i * j] = false;
        }
}

注意:这里我们还有一个小优化,我们的 j 是从 i 开始枚举的,这是因为在 j < i 时,我们一定在 i 更小的时候就已经标记过 i * j 了。这样可以避免一些重复标记。

但是,我们发现埃氏筛依旧会出现重复标记的情况。例如 7×8=56,我们会在 i=7 的时候筛一遍 56,但是因为 8 有质因子 2,所以在 i=2 时也会筛一遍 56

因为有重复标记,所以埃氏筛的时间复杂度略高于 O(n)。当 n 很大的时候,可以明显看出它和 O(n) 的差距。

【欧拉筛】

我们希望埃氏筛可以进化到 O(n) 的复杂度。

上面埃氏筛慢的原因,就在于一个数被多次标记。而多次标记的原因,是我们分类分的不好。
我们在上面把所有合数分类,这个分类的方法是 “如果 x 是质数 p 的倍数,我们就把 x 分入 p 的类中”。
并且,每找到一个质数,我们就把该质数对应的分类中所有的数都打上合数标记。

显然,这样的分类方法可能会让一个数被分入多个类中,从而导致多次标记。现在,我们需要一个新的分类方法,可以把所有合数无遗漏无重复的分类。

考虑一个合数 x 会被分进谁的类中。如果能把 x 分进它的一个因数的类中,我们就可以很方便地打标记。

既然我们要 x 的因数,不如直接取最简单的情况:把 x 分进它最大的真因数 x 的类中。
因为 x 是最大真因数,所以 xx 一定只差了一个质因子,并且是 x 的最小质因子 p

因为 px 的最小质因子,所以 x 的质因子一定都大于等于 p,于是我们可以枚举所有质数 a,满足 a 小于等于 x 的最小质因子,并把 x×a 打上标记。因为 x 的最小质因子都大于等于 a,所以这样可以保证 a 一定是 x×a 的最小质因数。

但是,对 x 找最小质因子挺麻烦的。我们注意到这么一个性质:当 a 达到上限,也就是当 a=x 的最小质因子时,a|x。因此,我们可以一直枚举 a,如果 a|x 就退出循环。

因为每个数都会唯一地分进一个类中,故这个算法是 O(n) 的。

int prime[60000006], cnt = 0; //cnt记录当前质数个数
bool p[60000005] = {};

void init() {
    for (int i = 2; i <= n; i++) {
        if (!p[i])
            prime[++cnt] = i;
			
            //j <= cnt:只能枚举当前找出来的质数
        for (int j = 1; j <= cnt && prime[j] <= n / i; j++) {
            p[i * prime[j]] = true;
            if (i % prime[j] == 0)
                break;
        }
    }
}

【运用线性筛思想解决问题】

分解质因数

对于一个数的分解质因数早已学过,但现在有了多次询问。

注意到,分解质因数的操作显然是一个可以递归的操作:如果要分解 xx 的最小质因数是 p,那么相当于要分解 x/p,并且在 x 的分解中加上一个 p

考虑记录 mn[x] 表示 x 的最小质因数。如果能求出所有数的 mn[],我们就可以在 log 的时间复杂度内回答一个询问。我们现在的任务就是预处理出 mn[] 数组。

还是一样的思路:对于一个数 x,求 mn[x] 很难,但是求出所有 mn[]=x 的很简单。

我们把每一个数都分进它最小质因数对应的类中。但是这样还是难以解决,所以我们考虑把每一个数都分进它最大真因数对应的类中。然后在枚举到一个数的最大真因数 i 时,枚举所有可能的最小质因数 prime[j],并记录 mn[i×prime[j]]=prime[j]

可以发现,我们只需要在 p[i * prime[j]] = true 之后加上一句 mn[i * prime[j]] = prime[j] 即可。

因数个数与因数之和

因数个数的公式很简单:先分解质因数,然后每个质因数的指数加一再相乘。
但是,我们希望这个公式可以完成递推。

当我们已经知道了 n 的因数个数 f(n),考虑乘上一个质数 p 后的 f(np) 会怎么变化。

  1. n 中不含 p,那么 f(np)=(1+1)×f(n)

  2. n 中含 p。因为我们的 p 实际上枚举到 n 的最小质因数就退出了,所以我们只需要考虑当 n 乘上了 n 的最小质因数后的变化。

    我们只需要同时记录下 n 的最小质因数的次数 mn[n],就可以方便地让 f(np)=f(n)÷(mn[n]+1)×(mn[np]+1)。(显然 mn[np]=mn[n]+1

经过这么一系列的递推,我们就可以求出所有数的因数个数了。同理,因数和也可以求出来,只是公式不同而已。

【总结】

可以发现,我们在数论上的递推与动态规划时的递推并不相同。我们在数论上的递推通常不是由 k1k,而是由 k/...k。而线性筛就给我们提供了一个很好的递推方法:枚举每一个数的最大真因数,然后乘上一个最小质因数进行递推。

【积性函数】

f(ab)=f(a)×f(b)|(a,b)=1,称 f() 为积性函数。

例如上面的因数个数就是一个积性函数。

积性函数就意味着我们可以进行递推。

【欧拉函数】

定义 φ(n) 为 “小于 n 的数中与 n 互质的数的个数”。

n 的质因子为 p1,p2,...,pk,则 φ(n)=n×i=1kpi1pi

因此,我们可以这样求欧拉函数。

如果我们要求 1nφ(),肯定不能每一个都求一次。所以我们用上面欧拉筛的方法递推。

递推code

仪仗队

如果一个人位于 (x,y),要想被看到,必须 (x,y)=1
于是这个题就变成了问有多少对数互质。当然,我们考虑一半即可,注意对角线只有一个能看到,特殊处理。

考虑对于每一列 a,都要问比 a 小的有几个与 a 互质。所以答案就是 φ(1)+φ(2)+...+φ(n)

【乘法逆元】

在题目中,我们经常需要取模。但是如果涉及除法,乱除可能导致答案错误。这时候,我们尝试用乘法在取模意义上代替除法。于是,乘法逆元诞生了。

ab1(modp),则记 a=b1=1b,b=a1=1a,称 a,b 互为逆元。(模 p 意义下)

注意:只有 a,bp 互质时才存在逆元。

逆元有什么用?如果我们要在模的意义下做除法,就相当于乘以除数的逆元。
那么如何快速求出逆元?

接下来我们做两件事。

  1. (a,p)=1,求证:a1modp 存在且唯一。

  2. O(logp) 的时间内求出 a1modp

首先提出两个概念:完全剩余系和简化剩余系。

完全剩余系:简称完系。x 的完系为一个包含 x 个数的集合,满足集合中任何两个数模 x 的余数各不相同。

简化剩余系:简称简系。x 的简系为一个包含 φ(x) 个数的集合,满足集合中任何两个数模 x 的余数各不相同,且与 x 互质。

x 的完系为 R(x)x 的简系为 R(x)


下证:若 (a,p)=1,求证:a1modp 存在且唯一。

R(p)={x1,x2,...,xk}。(k=φ(p)

显然,因为 (a,p)=1,所以 aR(p)。如果 a1 存在,若 (a1,p)1,则 a×a11,矛盾。因此 a1R(p)

因为逆元就是相乘得一,所以我们考虑把所有东西都乘起来看看。

我们只需证明:对于任意 a(a,p)=1,则 ax1,ax2,...,axk 依然构成模 p 的简化剩余系。

  1. ax1,ax2,...,axkφ(p) 个。且由于 (a,p)=(xi,p)=1,所以 (axi,p)=1

  2. 对于任意 i,jaxiaxj(modp)。这是因为 axiaxj(modp)paxiaxjpa(xixj)pxixj,矛盾。

因此,ax1,ax2,...,axk 也是模 p 的简系。显然简系中有一个 1。所以 x1,x2,...,xk 中恰有一个是 a1

证毕。


怎么求乘法逆元?

欧拉定理:对于 (a,p)=1,有 aφ(p)1(modp)

证明:考虑模 p 的简化剩余系 x1,x2,...,xk。由上面知 ax1,ax2,...,axk 也是简系。

所以 i=1kxiax1×ax2×...×axk(modp)

i=1kxi=X,则 Xaφ(p)X(modp)

(X,p)=1,(aφ(p),p)=1,所以 X,aφ(p)R(p)

故,若 aφ(p)1,则 aφ(p)X1XX,矛盾。

所以 aφ(p)1(modp),证毕。

由于 aφ(p)1(modp),所以 a×aφ(p)11,所以 a1=aφ(p)1。用快速幂求解即可。

【连续的乘法逆元】

连续乘法逆元

注意要求模数为质数。

如果对于每一个数,都用 aφ(p)1 算,就太慢了。

我们希望可以重复利用之前求出来的数据。

假设已知 1(k1) 的逆元,求 k 的逆元。(modp)

考虑带余除法p÷k=tr(0r<k)

p=kt+r,kt+r0(modp)

两边同时乘 k1r1利用逆元性质:相乘得一。

tr1+k10(modp)

k1tr1(p/k)(pmodk)1(modp)

所以,k1 可以用 (p%k)1 推出来。

初始设立 inv[1]=1,然后一路递推到 inv[n]


带余除法:a÷p=tr(0r<p)a=pt+r

如果我们想求 a 的某种属性,可以考虑用 p,t,r 三者的属性推出来。

【BSGS】

模板 BSGS

给定 bpn,求 l 使得 bln(modp)

首先有一个枚举的想法,O(p)

然后想到:

一个一个往前枚举太慢了,如果能先大幅度跨,然后再小幅度调整,时间就会快很多。


t=[p],l=at+r(0r<t)

因为 l 显然 pt=[p],所以 a 的级别是 p;因为 r<t,所以 r 的级别也是 p。最终的复杂度是 O(p) 的,是一个比较好的复杂度。

考虑用带余除法的方式转换式子。

blbat+r(bt)a×brn(modp)

我们枚举 a,考虑怎么求 r。当 a=a0 时:

(bt)a0×brn,brn×[(bt)a0]1

如果我们能找到一个 r 满足 brn×[(bt)a0]1,那么这个 ra0 组成一组可行的解,然后就能直接通过 l=at+r 求出对应的 l

但是,如果不能快速找到 r,时间并没有优化。那我们怎么快速找到 r

建立一个 map,索引时 n×[(bt)a0]1,键值是 r。这样当我们算出 n×[(bt)a0]1 时,我们可以立刻用 mp[n×[(bt)a0]1] 查询对应的 r

【具体实现时的修改】

在具体实现时,我们让 l=atr(1rt)

bl=batr=(bt)a÷brn

br(bt)a×n1

我们让 map[ br ] r,这样就可以用 map[ (bt)a×n1 ] 快速求 r

当然,我们也可以让 map[ br×n ] r,这样我们的式子就应该是 br×n(bt)a,调用 map[ (bt)a ] r 进行查询。

至于 l=atr 而非 l=at+r 的原因,是因为 map 里面查询不到会返回 0,如果 0r<t,会混淆不存在和 r=0

code

【裴蜀定理】

裴蜀定理

裴蜀定理:i=1nAixi=gcd(A1,A2,...,An) 一定有解。

二元形式:若 (a,b)=1,则 ax+by=1 有解。

考虑模 b 的简系,这个简系乘 a 也是简系,其中必有一个 1。所以存在 ax=bk+1,此时令 y=k 即可。

【拓展欧几里得算法】

【模板】二元一次方程组

判断无解:裴蜀定理,gcd(a,b) 是否整除 c

接下来尝试寻找一组整数解。一个简单的想法显然可以枚举 xO(min(a,b))。慢,考虑优化。

众所周知,辗转相除法可以求出 gcd(a,b)。那我们能否在辗转相除的过程中,顺便求出对应的解 (x,y) 呢?

辗转相除法:不妨 ab,gcd(a,b)=gcd(b,a%b)

ax+by=gcd(a,b) 的方程,考虑用辗转相除法递归:转化为 bx0+(a%b)y0=gcd(b,a%b),用 x0,y0 推出 x,y

这么做之所以好,是因为等式左侧的关系很明显,而右侧始终相等。

同样地,考虑辗转相除法的结束条件:a,b 中,一个是 0,另一个是最大公因数。
当我们递归结束时,方程变为 gcd(a,b)x0+0y0=gcd(a,b)

显然此时的有解 (1,0)

现在我们希望通过这一层的解推出上一层的解。
即已知 bx+(a%b)y=gcd(a,b) 有解 (x0,y0),求 ax+by=gcd(a,b) 的解 (x,y)

因为我们想要 b,a%b,所以考虑把 a 拆掉,a=[ab]×b+a%b
现在的方程变为 [[ab]]×b+(a%b)]x+by=gcd(a,b)

拆括号,b[[ab]x+y]+(a%b)x=gcd(a,b)。这不就是上一层的形式吗?

所以,令 [ab]x+y=x0,x=y0 即可。(x,y)=(y0,x0[ab]y0)

【中国剩余定理】

中国剩余定理(孙子定理)

M=i=1npi,Mi=M/pi。显然 (M,Mi)=1

不妨设 Mimodpi 逆元 Mi1

有特解:x0=i=1naiMiMi1

正确性:x0ai(modpi) 吗? x0 中的 aiMiMi1 贡献 ai 的余数,其余项的 M() 都包含 pi,所以最终的余数就是 ai

通解:x=x0+kM(kZ)

【阶与原根】

阶:amodp 的阶为最小的 r 使得 ar1(modp)

性质一:若 ak1(modp),必有 rk

证明:若 rk,记 k=tr+z(1z<r),则 az1(modp)z<r,与 r 最小矛盾。

性质二:akakr(modp)。证明显然。

由于 aφ(p)1(modp),所以 rφ(p)

如果 r=φ(p),称 r 为原根。

原根的性质:a1armodp 刚好构成一个 modp 的简系。

posted @   FLY_lai  阅读(20)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示