Typesetting math: 100%

数论 (1)

⚠WarningWarning

2022 年博主重读文章,发现有事实错误,语言表达也略欠缺,请理性对待文章内容(所以本文是议论文) .

本文数学公式较多,请耐心等待 .

好了,正片开始 .


目录


11 前言

这里的例题大部分用的是洛谷上的。

00 带余除法

a,bZa,bZ,我们将满足 bqabqa 的最大的 qq 记做 aa 除以 bb 的商,实际上就是 ababxxxx 向下取整),重点来了,对于这个 qq,我们记 r=abqr=abqaa 除以 bb 的余数,一般记做 amodbamodb

也就是我们小学学的:

a÷b=qra÷b=qr






11 整除







1.11.1 定义

a,bZa,bZ 且存在 qZqZ 使得 b=aqb=aq,则称 aa 整除 bb,记做 ababaa 不整除 bb 记做 abab
a,bZa,bZabab,则我们称 aabb 的约数/因数/因子,bbaa 的倍数。






1.21.2 性质

  1. ab,bcacab,bcac(传递性)。

  2. ab,acx,yZ,abx+cyab,acx,yZ,abx+cy(线性组合)。

  3. 一些比较显然的其他性质:

    1. dadkadadkakZkZ
    2. da,dbda±bda,dbda±b
    3. ab,baa=bab,baa=b

证明:

  1. abab,则存在 q1Zq1Z 使得 aq1=baq1=b(定义),同理存在 q2q2 使得 bq2=cbq2=c,代入得 aq1q2=caq1q2=c,即得 acac
  2. 先证引理
    引理 1:ababxababx
    证明:由定义得存在 qZqZ 使得 aq=baq=b,代入,aaqxaaqx,证毕。
    引理 2:abxZ,ab+xaabxZ,ab+xa
    证明:由定义得存在 qZqZ 使得 aq=baq=b,代入,xZ,aaq+xaxZ,aa(q+x)xZ,aaq+xaxZ,aa(q+x),证毕。
    有了引理,命题就很显然了,先用 1 再用 2 就证出来了。





1.31.3 拓展





1.3.11.3.1 素数




1.3.1.11.3.1.1 定义

若一个正整数 qq 只有 11qq 两个因子,则称 qq 为素数(Prime)。




1.3.1.21.3.1.2 分解质因数/分解素因子

算术基本定理(唯一分解定理):对于所有 aN+aN+aa 均可被唯一的分解为 a=Oi=1priia=Oi=1prii,其中 pipi 均为素数,OO 称为 aa 的素因子个数,一般记为 ω(a)ω(a),但是 OI 一般不会用到(指 ω(a)ω(a))。

板子:洛谷 P2043




1.3.1.31.3.1.3 算法

第一!朴素的 O(n)O(n) 判断素数:

bool Isprime(int n)
{
    for (int i=2;i*i<=n;i++) // 因子是以 i,n/i 对称的,所以只需要枚举 [1,√n] 的数即可;并且注意 i=2 开始。
    if (!(n%i)) return false; // !(n%i) == (n%i==0),这句话的意思就是如果 i 是 n 的因子。
    return true; // 没有其他因子,那么就是素数喽
}

第二!埃氏筛

我们考虑维护一个数组 isprime,然后直接把所有素数的倍数都标记为 false,判断时就可以直接 isprime[n] 了。
我们只需要遍历即可遍历所有素数(我们将 isprime[n]=false 的不进行筛,因为整除性),证明:
数学归纳法,[2,2][2,2] 素数显然成立,如果目前把 [2,n][2,n] 的素数都筛完了,那么如果 isprime[n+1]=false 成立;否则,如果它不是素数,那么它一定有一个 [2,n1][2,n1] 之内的因子,但是如果它有这个因子早就被筛掉了,证毕。

gif 图片如下:

aSTFqx.gif

Code:

const int N=???;
bool isprime[N]; // 为了省去 memset,我们把 isprime[i]=false 当做其是素数。
int n; // 筛 1~n 素数
void GetPrime()
{
	isprime[0]=isprime[1]=true;
	for (int i=2;i*i<=n;i++) // 用 i*i 同上个算法。
	{
	if (isprime[i]) continue;
	for (int j=i+i;j*j<=n;j+=i){isprime[j]=true;} // 筛
	}
}

时间复杂度大约是 O(nloglogn)O(nloglogn)?因为 loglognloglogn 已经很接近 11 了,所以是近线性复杂度。


第三!真正线性的筛法!洛谷 P3383(模板)
这筛法还能顺便给个素数表。

核心:每个合数只被它最大的非自身的因数筛掉。

所以加上素数表,如果遇到素数直接 break; 即可。

const int N=???,M=???;
int prime[N],isprime[M],pi_; // pi_ 这个命名其实是表示 π(n) 函数
void GetPrime()
{
	isprime[0]=isprime[1]=true;
	for (int i=2;i<=n;i++)
	{
		if (!isprime[i]) prime[pi_++]=i;
		for (int j=0;(j<pi_)&&(i*prime[j]<=N);j++) // j 用来枚举所有素数,我们都有素数表了就不用枚举了。
		{
			isprime[i*prime[j]]=1;
			if (!(i%prime[j]))break; // Core!!!!!!!!!!!! 只筛一次!!!!!!!!!
		}
    }
}

MR 算法,仅供拓展。(判素用的,有点误差,但是复杂度接近 O(1)O(1)),SPOJ288 -- PON Prime or Not 模板

一想到 O(1)O(1),我们不妨试一下随机算法

按理来讲,我们应该是在 [1,n][1,n] 里面随机,再通过某些方式来提高正确率的。但是这样做太慢 QAQ,我们要确保每一个在 [1,n][1,n]

中的数都不是 nn 的因子,显然用随机试的算法是行不通的。那么,我们该怎么办?

数论中,我们有 Fermat 小定理:

pp 是素数 xpp(modp)xpp(modp),即 xp11(modp)xp11(modp)

这个定理对于任何质数成立;但是反过来不一定。满足 Fermat 小定理的数不一定是质数。事实上,如果对任意bN,bp11(modp)bN,bp11(modp),我们就称 pp 为 Carmichael 数(伪 素 数)。运用 Fermat 小定理,我们可以判定一个数不是质数,但是不能判定一个数是质数(这个方法叫费马素性测试)。

不过,这给我们的随机枚举提供了一个不错的启示:不是随机枚举 nn 的因子,而是一个基底 bb。若对于不同的基底,都有bp11(modp)bp11(modp)pp 是质数的可能性就更大了。事实上,如果 pp 满足 bp11(modp)bp11(modp),那么 pp 就叫做"基于 bb 的伪素数"。

证明如下:

证明图片.png

引入二次探测定理:

这个定理叫做“二次”是有原因的。其一,它是费马小定理探测质数的辅助工具,作为第二道屏障;其二,它和一个"二次同余方程"有关。定理如下:

x21(modp)x21(modp),则 x1(modp)x1(modp)xp1(modp)xp1(modp)

这个定理比较好证明:

证明图片.png

对于一个质数 pp,只要 pp 和二次探测定理不符,那么我们就可以肯定 pp 不是质数、事实上,我们常常直接用这个定理去判定质数,而不是用费马小定理。

无论是费马小定理还是二次探测定理,我们都需要选取若干个合适的基底来进行测试。一般而言,我们会选取这五个基底: 2,3,7,61,242512,3,7,61,24251。当需要测试的数据规模在 10161016 左右时,它的表现效果还是可以的。但当数据规模更大些时,你必须考虑将基底扩大一些。在这个基底的基础上,可以考虑选取前 10151015大的质数,从而解决几乎 100%100% 的数据。

对于每一个基底 bb 和一个待判断的数 xx,我们进行如下测试:

  1. 用费马小定理判定一下这个数是不是合数。
  2. 如果 x1x1 是偶数,我们可以对费马小定理做如下变形:

bx11(modx)bx12×21(modx)bx11(modx)bx12×21(modx)

这样就可以用二次探测定理看一下有没有 bx121bx121x1(modx)x1(modx) 了。如果都不满足, xx 就一定不是质数。

如果 x12x12 同样是一个偶数,我们可以继续将它拆成 x14×2x14×2,然后再用一次二次探测定理来做。

  1. 对于二次探测定理 Xk1(modx)Xk1(modx) ,如果 kk 是奇数或者 Xx1(modx)Xx1(modx),此时我们没有办法再拿这个数做文章了。我们只能暂时认定 xx 是质数。

注意到这就是 Miller Rabin 算法具有随机性的证据之二:对质数的判定不充分。但事实上,用上面这种方法已经可以成功地判定许多质数了,在实际应用中是值得信赖的。

上面这个算法的时间复杂度是O(log2n)O(log2n)的:瓶颈在于快速幂和分解待验证的数;但实际运行时,速度是相当快的。

显然,我们不难看出上述代码有大量需要优化的地方。就比如说,开始的那次费马小定理的判定是完全没有必要的。我们可以直接把这一过程留到二次探测定理去执行。
其次,我们使用二次探测定理时每次都要探测 lognlogn 次,这个次数是可以稍微有所优化的。

对于前者,我们直接删去开头的费马小定理判断就可以了。对于后者,网上广为流传这样一种优化技巧:

k=x1=2p?dk=x1=2p?ddd 是一奇数,那么之前二次探测定理 X21(modx)X21(modx) 检测的数 XX 分别是如下几个数:b2p×d,b2p1×d,b2p2×d,bdb2p×d,b2p1×d,b2p2×d,bd

如果我们从后往前检测,如果其中一个数 XX 通过了二次探测定理,就直接判定 xx 是质数。

这个新算法的流程如下:

  1. 按上面的方法计算出 ddpp。记 cur=bdcur=bd,如果 cur1cur1x1(modx)x1(modx)就直接判定 xx 是质数。
  2. 每次把 curcur 赋值为 cur2modxcur2modx直到 cur=bkcur=bk。这个过程等价与将 curcurbdbdbkbk 的方向扫描。
  3. 如果 cur=x1cur=x1 则直接判定 xx 为质数。否则转 22
  4. 如果上述测试都没有判定 xx 为质数,则直接判定 xx为合数。

代码就不给了。




1.3.1.41.3.1.4 题目

洛谷 P1876

因为因子对于 i,n/i 对称,再由于埃氏筛原理,应该每个数被按开关的次数就是 d(n)d(n),因为对称,所以显然大多数 d(n)d(n) 是偶数,也就是最后是关着灯的。
当然,如果有一个 i 使得 i=nii=ni,它就是开着的那个灯了。
我们可以发现,这种 nn 只有完全平方数且 i=ni=n

Code:

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
	int now=1,n;
	cin>>n;
	while (now*now<=n) cout<<now*now<<' ',now++;
	return 0;
}




1.3.21.3.2 GCD & LCM




1.3.2.11.3.2.1 定义

OIWiki - GCD

a1,a2a1,a2 是两个不全为 00 的整数,若 da1da1da2da2,我们则称 dda,ba,b 的公约数,我们将最大的 dd 称为 a1,a2a1,a2 的最大公约数(GCD),数学上一般记做 (a1,a2)(a1,a2),OI 里一般记做 gcd(a1,a2)gcd(a1,a2)
gcd(a1,a2)=1gcd(a1,a2)=1,则称 a1,a2a1,a2 互素/互质(可以记做 a1a2a1a2)。

a1,a2a1,a2 是两个不全为 00 的整数,若 a1la1la2la2l,我们则称 lla,ba,b 的公倍数,我们将最小的 ll 称为 a1,a2a1,a2 的最小公倍数(LCM),数学上一般记做 [a1,a2][a1,a2],OI 里一般记做 lcm(a1,a2)lcm(a1,a2)

在这篇文章中,GCD 与 LCM 记做 gcd(a,b)gcd(a,b)lcm(a,b)lcm(a,b);而 (l,r)(l,r)[l,r][l,r] 表示区间。




1.3.2.21.3.2.2 性质

  1. 定理:对于 a,bN+a,bN+ ,我们找出 a,ba,b 的质因数分解中相同的部分,然后算出它的值,就是 gcd(a,b)gcd(a,b) 啦。
                   如果你把 gcd(a,b)gcd(a,b) 只算一次,然后把 a,ba,b 剩余的部分也加上,就是 lcm(a,b)lcm(a,b) 喽。
  2. 显然,我们由 1 得到了一个重要性质:gcd(a,b)lcm(a,b)=abgcd(a,b)lcm(a,b)=ab
  3. 辗转相除:gcd(a,b)=gcd(b,amodb)gcd(a,b)=gcd(b,amodb)
  4. 辗转相减(其实叫「更相减损」的;和辗转相除同出一源):gcd(a,b)=gcd(b,ab)gcd(a,b)=gcd(b,ab)



1.3.2.31.3.2.3 算法

计算 gcdgcdlcmlcm

// 辗转相除,O(log n) 的,用斐波那契数列的相邻两项能卡到 n^2。
int gcd(int a,int b){return b?gcd(b,a%b):a;} // 对于 b=0 辗转相除法是要特判的
int lcm(int a,int b){return a/gcd(a,b)*b;} // 套公式 gcd(a,b)*lcm(a,b)=a*b,改变了运算顺序防止爆掉 int

但是如果用高精的话([SDOI2009] SuperGCD)就用更相减损术,减法比取模算的快。




1.3.2.41.3.2.4 题目

洛谷 P6068

随便推推式子可知要最大化答案必须得寻找一个最小的 kN+kN+ 且满足 k6k6knkn 然后用 nn 除一下。

直接分解质因数大力计算即可。

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
const int N=1e7+5;
int t,n,m,h,s[N];
int main()
{
//	freopen("input.in","r",stdin);
	cin>>t;
	while (t--)
	{
		bool q=false;
		cin>>n; h=0;
		for (int i=1;i*i<=n;i++) // 分解
			if (!(n%i)){++h; s[h]=i; ++h; s[h]=n/i;}
		sort(s+1,s+1+h);
		for (int i=h;i>=1;i--)
		{
			if (n/s[i]>5){cout<<s[i]<<'\n';q=true;break;}
		} if (!q) cout<<"-1\n";
	} // 反着来的
	return 0;
}

洛谷 P1888

众所周知 sin 用短直角边和斜边除一下就行了,也就是最短边除一下最长边。

约分同时约 gcdgcd 即可,放到例题里真没什么必要。

#include<iostream>
#include<algorithm> // <algorithm> 里面有 __gcd 直接求 GCD,但是 NOIP 不能用。
#include<cstdio>
using namespace std;
int main()
{
	int a,b,c;
	cin>>a>>b>>c;
	cout<<min(a,min(b,c))/__gcd(min(a,min(b,c)),max(a,max(b,c)))<<'/'<<max(a,max(b,c))/__gcd(min(a,min(b,c)),max(a,max(b,c)));
	return 0;
}

NOIp2001普及组 最大公约数与最小公倍数问题

众所周知 gcd(a,b)lcm(a,b)=abgcd(a,b)lcm(a,b)=ab,然后暴力即可。

#include<iostream>
using namespace std;
int gcd(int x,int y) {return y==0?x:gcd(y,x%y);}
int lcm(int x,int y) {return x*y/gcd(x,y);}
int main()
{
	int x0,y0,s=0;
	cin>>x0>>y0;
	for (int p=1;p<=y0;p++)
		for (int q=1;q<=y0;q++)
			if (gcd(p,q)==x0&&lcm(q,p)==y0) s++;
	cout<<s;
	return 0;
}

洛谷 P1372

nk,2nk,3nk,,knknk,2nk,3nk,,knk 即可,显然这些数的 gcdgcdnknk

Code:

#include<iostream>
using namespace std; // n / k   P r o b l e m
int main()
{
	int n,k;
	cin>>n>>k;
	cout<<n/k;
	return 0;
}






22 同余






2.12.1 定义

mN+mN+a,bZa,bZ,若 mabmab,则称 aabbmm 同余,记做 ab(modm)ab(modm)

其实就是 amodm=bmodmamodm=bmodm 啦。





2.22.2 性质

  1. ab(modm)a±kb±k(modm)akbk(modm)akbk(modm)ab(modm)a±kb±k(modm)akbk(modm)akbk(modm)kZkZ 且对于上述公式有意义)
  2. ab(modm),cd(modm)a±cb±d(modm)ab(modm),cd(modm)a±cb±d(modm)
  3. pp 为素数,则 apa(modp)apa(modp),当且仅当 apap 时,ap11(modp)ap11(modp)




2.32.3 拓展




2.3.12.3.1 同余类、剩余系、简系与欧拉函数



2.3.1.12.3.1.1 定义

定义对于给定正整数 mmCr={xZxr(modm)}Cr={xZxr(modm)} 。我们称 C0m1C0m1 为模 mm 的 同余类/剩余类,显然 C0m1C0m1 构成 ZZ 的一个划分。

aiCiaiCi,即可得到模 mm 的一个完全剩余系 aa,常用的是 1,2,m11,2,m1,我们称其为模 mm 的最小完全剩余系。

对于取的 aimaim 的情况,aiai 称为模 mm 的一个简系。

欧拉函数 φ(n)φ(n) 是一个定义在正整数集上的函数,模 mm 的一个简系的元素个数称为 φ(n)φ(n),也可称作 1n1nnn 互素的数的个数(也可写作 0n10n1nn 互素的数的个数),可表示为 φ(n)=ni=1[in]φ(n)=ni=1[in]
[x][x] 为条件符号,若 xx 为真则返回 11,否则返回 00,相当于 bool(x)


2.3.1.22.3.1.2 性质

剩余系什么的没啥性质,主要是欧拉函数。

  1. m,nm,n 互质,则 φ(nm)=φ(n)φ(m)φ(nm)=φ(n)φ(m)(积性函数)
  2. nn 为奇数,则 φ(2n)=φ(n)φ(2n)=φ(n)
  3. 通式:若 n=si=1paiin=si=1paii,则 φ(n)=nsi=1(11pi)φ(n)=nsi=1(11pi)
  4. 欧拉定理:若 a,mN+a,mN+amamm2m2,则 aφ(m)1(modm)aφ(m)1(modm)
  5. 1n1nnn 互素的数之和为 12nφ(n)12nφ(n)
  6. 对于任意正整数 nndnφ(d)=ndnφ(d)=n

2.3.1.32.3.1.3 算法

首先当然是普通求 φ(n)φ(n) 的算法:

// 第一种写法:暴力
// 时间复杂度:O(nlogn)
int phi(int n)
{
	int ans=0;
	for (int i=1;i<=n;i++) if (gcd(i,n)==1) ++ans; // gcd 这里没写
	return ans; 
}

// 第二种写法:套通式
// 时间复杂度:O(sqrt(n))

int phi(int n)
{
    int ans=n;
    for (int i=2;i*i<=n;i++) // 分解素因子 
        if(!(n%i))
        {
            ans=ans/i*(i-1);
            while (!(n%i)) n/=i;
        }
    if (n>1) ans=ans/n*(n-1);
    return ans;
}

线性筛欧拉函数(埃氏筛同理)。

根据欧拉函数和素因子的关系(通式),就仿照线性筛素数做就行。

const int N=???
int n,phi[N],prime[N],tot,ans;  
bool isprime[N];  
void getphi() // and prime???
{   
    phi[1]=1;  
    for (int i=2;i<=N;i++)
    {  
        if (!isprime[i]){prime[++tot]=i; phi[i]=i-1;} // phi 一个素数答案显然是 n-1(2~n) 
        for (int j=1;j<=tot;j++)  
        {  
            if (i*prime[j]>N) break;  
            isprime[i*prime[j]]=1;
            if (i%prime[j]) phi[i*prime[j]]=phi[i]*(prime[j]-1); // 这里用了欧拉函数的积性 
            else{phi[i*prime[j]]=phi[i]*prime[j]; break;}
        }  
    }
}

2.3.1.42.3.1.4 题目

洛谷 P2158 [SDOI2008]仪仗队

不难得出判定的写法是互质,然后随便推一下就会发现答案就是 2n1i=0φ(i)+12n1i=0φ(i)+1

主要是套板子,代码就不给了。


2.3.22.3.2 同余方程(组)



2.3.2.12.3.2.1 定义

设整系数多项式 f(x)=ni=0aixif(x)=ni=0aixi,同余式 f(x)0(modm)f(x)0(modm) 称为模 mm 的同余方程,若整数 x0x0 满足 f(x0)0(modm)f(x0)0(modm),则称 x0x0 为同余方程的解。显然 xx0(modm)xx0(modm) 都是同余方程的解,我们把它看做同余方程的 同一个解。


2.3.2.22.3.2.2 性质
  1. f(x)0(modm)f(x)0(modm) 最多有 mm 个解(显然)
  2. f(x),g(x)f(x),g(x) 都是整系数多项式,f(x)0(modm)f(x)+ms(x)0(modm)f(x)+s(x)s(x)(modm)f(x)0(modm)f(x)+ms(x)0(modm)f(x)+s(x)s(x)(modm)
  3. amam,则 f(x)0(modm)af(x)0(modm)f(x)0(modm)af(x)0(modm)

2.3.2.32.3.2.3 算法

2.3.2.3.02.3.2.3.0 [前置] 拓展欧几里得算法 exgcd

拓展欧几里得算法(exgcd),可以用来找到形如 ax+by=gcd(a,b)ax+by=gcd(a,b) 的方程的一组特解

由裴蜀定理知,原方程一定有解。

我们利用辗转相除法(普通欧几里得算法)。

我们设 d=gcd(a,b)d=gcd(a,b)

我们可以知道,我们辗转相除法的边界是 a=d,b=0a=d,b=0,此时我们可以知道 aa 就是最大公约数,我们还可以知道,在这时一定有一解为 x=1,y=0x=1,y=0,即 1×a+0×b=d1×a+0×b=d

我们知道 gcd(a,b)=gcd(b,amodb)gcd(a,b)=gcd(b,amodb),如果我们可以推导出每一次的解 xxyy,与相除后的解 xxyy 的关系;我们就可以算出其中的一个解了,(xxyy 相当于是 aabb 的解,xxyyaa 变成了 bbbb 变成了 amodbamodb 时的解(辗转相除))。

轻易得知:

{ax+by=dbx+(amodb)y=d{ax+by=dbx+(amodb)y=d

则:

bx+(abab)y=dbx+aybaby=day+b(xaby)=d解得:{x=yy=xabybx+(abab)y=dbx+aybaby=day+b(xaby)=d:{x=yy=xaby

然后我们知道 xxxxyyyy, 的关系后就可以求解了:

#include<iostream>
#include<cstdio>
using namespace std;
void exgcd(int a,int b,int& x,int& y) //x.y也可以用pair返回,这里用了引用
{
	if (!b){x=1;y=0;return ;}       //边界
	gcd(b,a%b);                     //辗转相除
	int tmp=y; y=x-(a/b)*y; x=tmp;  //套公式
}
int main()
{
	int a,b,x,y;
	scanf("%d %d",&a,&b);
	exgcd(a,b,x,y);
	printf("%d %d",x,y);
	return 0;
}

2.3.2.3.02.3.2.3.0 [前置] 逆元/数论倒数

1.定义

我们称满足 aa11(modm)aa11(modm)a1a1 称为 aamm 意义下的逆元(也叫数论倒数)。

逆元是模意义下的除法。

2.算法

普通求逆元用 exgcd 求解即可:

#include<iostream>
#include<cstdio>
using namespace std;
void exgcd(int a,int b,int& x,int& y)  //拓展欧几里得
{
    if (!b){x=1;y=0;return ;}
    exgcd(b,a%b);
    int tmp=x; x=y; y=tmp-a/b*y;
}
int main()
{
    int a,b,x,y;
    scanf("%d %d",&a,&b);
    exgcd(a,b,x,y);
    printf("%d",(x+b)%b);
    return 0;
}

素数的逆元:众所周知,费马小定理是:

pp 为素数,则 xpx(modp)xpx(modp)

当且仅当 xpxp 时:xp11(modp)xp11(modp)

然后我们用后面的式子同除以一个 xx,可以得到 x1xp2(modp)x1xp2(modp),所以我们只要求出 xp2modpxp2modp 就可以了,用快速幂求解即可。

Code:

#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
ll qpow(ll x,ll y,ll mod)  //快速幂
{
    ll ans=1,base=x;
    while (y)
    {
        if (y&1) ans*=base;
        base*=base;y>>=1;
    }
    return ans;
}
int main()
{
	ll x,p;
	scanf("%lld %lld",&x,&p);
	printf("%lld",qpow(x,p-2,p));
	return 0;
}

线性筛逆元

线性筛肯定是 O(n)O(n) 的嘛。

首先我们设 p=ki+rp=ki+r,然后可以知道 ki+r0(modp)ki+r0(modp)(因为 ki+r=pki+r=p,所以 pmodp=0pmodp=0)。
然后我们两边同乘 r1i1r1i1,就得到 kr1+i10(modp)kr1+i10(modp),移项得到 i1kr1(modp)i1kr1(modp)
我们可以知道 k=pi,r=pmodik=pi,r=pmodi,所以我们得到公式:i1pi×pmodi1(modp)i1pi×pmodi1(modp)

#include<iostream>
#include<cstdio>
using namespace std;
const int N=???;
typedef long long ll;
ll inv[N];
void GetInv(ll n,ll p)
{
	for (int i=2;i<=n;i++)
		inv[i]=(-(p/i)*inv[p%i]%p+p)%p;
}

线性筛阶乘逆元

也就是线性筛 n!1n!1

我们设 inviinvi 表示 i!i! 的逆元,我们可以轻易知道 invi+1=(1i+1)!1invi+1=(1i+1)!1,我们同乘 i+1i+1 就变成了,invi+1(i+1)=(1i!)1=inviinvi+1(i+1)=(1i!)1=invi,所以我们可以得到:invi+1(i+1)=inviinvi+1(i+1)=invi

所以我们先求出 n!n! 的逆元,再倒推回来即可。

#include<iostream>
#include<cstdio>
using namespace std;
const int N=???
typedef long long ll;
ll inv[N]; 
void GetFactInv()
{
	inv[1]=inv[0]=1;
   	for (int i=1;i<=n;i++)  //求阶乘
   		inv[i]=(inv[i-1]*i)%p;
   	inv[n]=GetInv(inv[n],p); //求n!的逆元
   	for (int i=n-1;i>=1;i--)//倒推 
   		inv[i]=(inv[i+1]*(i+1))%p;
   return 0;
}

2.3.2.3.02.3.2.3.0 [前置] 阶

定义:满足 ax1(modp)ax1(modp) 的最小正整数 xx 称作 aapp 的阶,写作 aa(显然条件是 apap)。

性质:

  1. aφ(p)aφ(p)
  2. a0,a1,a2,,aa1a0,a1,a2,,aa1 两两不同。
  3. axay(modp)axay(modp) 的充要条件是 xy(moda)xy(moda)

2.3.2.3.02.3.2.3.0 [前置] 原根

定义:满足 gg 关于模 pp 的阶等于 φ(p)φ(p)ggpp 的一个原根。

性质:

  1. grx(modp)grx(modp)rr 是唯一的,反之亦然(ggpp 的原根),x<px<pr<φ(p)r<φ(p)
  2. 质数的原根个数是 φ(φ(p))φ(φ(p))
  3. 一个模数存在原根的充要条件是,这个模数可以表示为1,2,4,p,2p,pn1,2,4,p,2p,pn其中 pp 是奇质数,nn 是任意正整数。

算法:考虑到最小的原根一般比较小,所以我们枚举原根然后判断。
假设我们当前枚举到 ii,判断的时候只需要枚举 φ(p)φ(p) 的质因子 p1kp1k。然后判断 iφ(p)pjiφ(p)pj 是不是全部都不是 11,如果全部都不是 11ii 就是 pp 的一个原根。


2.3.2.3.12.3.2.3.1 形如 axb(modm)axb(modm) 的线性同余方程

定理:

a,bZa,bZ,一元一次同余方程 axb(modm)axb(modm) 有解的充要条件是 gcd(a,m)pgcd(a,m)p,其解数为 gcd(a,m)gcd(a,m)
x0x0 为其一解,那么他的 gcd(a,m)gcd(a,m) 个解分别为 xx0+mgcd(a,m)×t(modm)xx0+mgcd(a,m)×t(modm),其中 t=0,1,,gcd(a,m)1t=0,1,,gcd(a,m)1

读者自证不难(逃)。

有了后面这个关系就可以 exgcd 求所有解,比如 NOIp2012 提高组 洛谷 P1082 就是个 b=0b=0 的特殊情况。


2.3.2.3.22.3.2.3.2 形如 axb(modm)axb(modm) 的同余方程

amam 时,使用 BSGS(北上广深 百事公司 Baby Step Giant Step 大步小步)算法,复杂度 O(p)O(p)

x=pmqx=pmq,然后原式就成了 apmbaq(modm)apmbaq(modm),然后右边枚举 bb 再用 hash 存起来(用 map 也不错),左边枚举 aa 再一个个判即可。

// 核心代码
int m=sqrt(p)+1; Hash.Clear();
for (int i=0,t=z;i<m;++i,t=1ll*t*y%p) Hash.Insert(t,i);
for (int i=1,tt=fpow(y,m,p),t=tt;i<=m+1;++i,t=1ll*t*tt%p)
{
	int k=Hash.Query(t);
	if (k==-1) continue;
	printf("%d\n",i*m-k);return ;
}

对于 a⊥̸ma/m(???)时,使用 exBSGS 算法:

d=gcd(y,p)d=gcd(y,p),然后将方程改成等式 ax+kp=bax+kp=b,注意到 bb 必须是 dd 的倍数否则无解,所以除一下 ddadax1+kpd=bdadax1+kpd=bd,然后无限迭代成 ykdyxkbd(modpd)ykdyxkbd(modpd),所以 BSGS 求一下在还原回去即可。

// 核心代码
void ex_BSGS(int y,int z,int p)
{
	if (z==1){puts("0");return;}
	int k=0,a=1;
	while (19260817) // 变换
	{
		int d=__gcd(y,p);if(d==1)break;
		if (z%d){NoAnswer(); return ;}
		z/=d;p/=d;++k;a=1ll*a*y/d%p;
		if (z==a){printf("%d\n",k); return ;}
	}
	Hash.clear();
	int m=sqrt(p)+1; // BSGS
	for (int i=0,t=z;i<m;++i,t=1ll*t*y%p) Hash.Insert(t,i);
	for (int i=1,tt=fpow(y,m,p),t=1ll*a*tt%p;i<=m;++i,t=1ll*t*tt%p)
	{
		int B=Hash.Query(t);
		if (B==-1) continue;
		printf("%d\n",i*m-B+k); return ;
	}
	NoAnswer();
}

2.3.2.3.32.3.2.3.3 形如 xab(modm)xab(modm) 的高次同余方程

this,目前看不懂,等看懂了补上。


2.3.2.3.42.3.2.3.4 形如 {xa1(modm1)xa1(modm1)xan(modmn)⎪ ⎪ ⎪⎪ ⎪ ⎪xa1(modm1)xa1(modm1)xan(modmn) 的线性同余方程组





33 数论函数

一般地,可把数论函数看做是整数集上定义的函数。





3.13.1 积性函数与完全积性函数

3.1.13.1.1 定义

ff 是一个数论函数,且对于任意 abab 均有 f(ab)=f(a)f(b)f(ab)=f(a)f(b),则称 ff 为积性函数。
ff 是一个数论函数,且对于任意 a,ba,b 均有 f(ab)=f(a)f(b)f(ab)=f(a)f(b),则称 ff 为完全积性函数。

3.1.23.1.2 性质

由唯一分解定理知,对于任意积性函数 ff 一定可以表示为 f(x)=ω(x)i=1paiif(x)=ω(x)i=1paii,其中 pa11pa22pa11pa22xx 的标准分解(然后就可以直接转换递归做了)。





3.23.2 一些特殊数论函数





3.2.13.2.1 素数计数函数 π(n)π(n)



3.2.1.13.2.1.1 定义

素数计数函数 π(n)π(n) 为不超过 nn 的素数个数,即 π(n)=ni=1[i is a prime]π(n)=ni=1[i is a prime]




3.2.23.2.2 单位函数 ϵ(n)ϵ(n)



3.2.2.13.2.2.1 定义

ϵ(n)=[n=1]ϵ(n)=[n=1],也就是 ϵ(n)={1n=10otherwise.ϵ(n)={1n=10otherwise.
显然,单位函数是完全积性函数。




3.2.33.2.3 除数函数 σk(n)σk(n)



3.2.3.13.2.3.1 定义

σk(n)σk(n) 就是 nn 的所有因子的 kk 次方和,即 σk(n)=dndkσk(n)=dndk

特殊的,σ0(n)σ0(n)nn 的因数个数,常记做 d(n)d(n)σ1(n)σ1(n)nn 的因数和,常记做 σ(n)σ(n)

除数函数是积性函数。




3.2.43.2.4 欧拉函数 φ(n)φ(n)

前面同余讲了




3.2.53.2.5 函数 I(n)I(n)、幂函数 Idk(n)Idk(n) 与莫比乌斯函数 μ(n)μ(n)

定义 II 为取值常为 11 的函数,定义幂函数 Idk(n)=nkIdk(n)=nk,对于 k=1k=1 时,Id1Id1 常记做 IdId

莫比乌斯函数 μ(n)μ(n) 将在章节「3.43.4 莫比乌斯函数与莫比乌斯反演」处展开。

μ(n)μ(n) 是积性函数。




3.33.3 狄利克雷(Dirichlet)卷积




3.3.13.3.1 定义

f,gf,g 为数论函数,则我们称 ffgg 的 Dirichlet 卷积 fg=ij=nf(i)g(j)=dnf(d)g(nd)fg=ij=nf(i)g(j)=dnf(d)g(nd),后面那个是一般用的表示形式。




3.3.23.3.2 性质

  1. 交换律:fg=gffg=gf
  2. 结合律:fgh=f(gh)fgh=f(gh)
  3. 分配律:fh+gh=(f+g)hfh+gh=(f+g)h
  4. 结合律 II:(xf)g=x(fg)(xf)g=x(fg)xx 是一个系数)
  5. 单位元:ϵf=fϵf=f



3.3.33.3.3 有趣的性质

  1. σk=1Idkσk=1Idk
  2. Id=φ1Id=φ1
  3. d=11d=11
  4. φ=μIdφ=μId



3.3.43.3.4 题目

给定 nn1n2321n232),求 ni=1gcd(i,n)ni=1gcd(i,n)

简单推式题:

ni=1gcd(i,n)=dnd×ni=1[gcd(i,n)=d]=dnd×nd|i=1[gcd(i,nd)=1]=dnd×φ(nd)ni=1gcd(i,n)=dnd×ni=1[gcd(i,n)=d]=dnd×nd|i=1[gcd(i,nd)=1]=dnd×φ(nd)




3.43.4 莫比乌斯函数与莫比乌斯反演




3.4.13.4.1 莫比乌斯函数 μ(n)μ(n)



3.4.1.13.4.1.1 定义

莫比乌斯函数 μ(n)=pPrime(1)[pn][p2n]μ(n)=pPrime(1)[pn][p2n],但是主流的定义好像是 μ(n)={1n=1(1)sn=si=1pi(piPrime)0otherwise.μ(n)=⎪ ⎪ ⎪⎪ ⎪ ⎪1n=1(1)sn=si=1pi(piPrime)0otherwise.



3.4.1.23.4.1.2 性质

  1. μ(n)0μ(n)0 当且仅当 nn 无平方因子。

  2. μμ 是积性函数(前面有)

  3. μ1=ϵμ1=ϵ

3.4.1.33.4.1.3 算法

线性筛

void getMu() {
	mu[1] = 1;
		for (int i = 2; i <= n; ++i) {
			if (!flg[i]) p[++tot] = i, mu[i] = -1;
			for (int j = 1; j <= tot && i * p[j] <= n; ++j) {
				flg[i * p[j]] = 1;
				if (i % p[j] == 0) {
					mu[i * p[j]] = 0;
					break;
				}
			mu[i * p[j]] = -mu[i];
		}
	}
} //OI wiki


3.4.23.4.2 莫比乌斯反演



3.4.2.03.4.2.0 [前置] 数论分块

posted @   yspm  阅读(418)  评论(1编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
😅​
点击右上角即可分享
微信分享提示