基础数论知识

前言

基础数论知识。

original edition 2023.3.29。

upd 2023.6.19:为明天听 zyw 大佬讲题复习,并优化 Latex。

upd 2023.6.20:扩展欧几里得,同余最短路,逆元,中国剩余定理及扩展。

upd 2023.6.21:BSGS。

upd 7.2:高斯消元。

upd 10.20: csp-s 考前复习,更正逆元板块一些错误。

知识点

线性筛

  1. 原理:让每个数被它最小质因数筛一次。
  2. imodprime[j]=0,则 iprime[j+k],k0 的最小质因数为 prime[j] 而非 prime[j+k]。此时筛完 break。

代码实现:

for(int i=2;i<=n;i++)
{
	if(!v[i]) p[++k]=i;
	for(int j=1;j<=k&&i*p[j]<=n;j++)
  	{
  		v[i*p[j]]=1;
	  	if(i%p[j]==0) break;
	}
}

唯一分解定理

  1. N=p1c1p2c2...pmcm
  2. N 正约数个数:i=1m(ci+1)
  3. N 正约数和: i=1m(j=0ci(pi)j)

最大公约数

  1. ab,gcd(a,b)=gcd(b,ba)=gcd(a,ba)
  2. b0,gcd(a,b)=gcd(b,amodb)

证明:

  • a<b,gcd(b,amodb)=gcd(b,a)
  • ab,设 a=qb+r,0r<b,r=amodb
  • da,dbda,b 公约数集合任一数。则 d(aqb)dr。所以 b,amodb >和 a,b 有相同公约数集合,故最大公约数相同。

tip : 设 a=k1d,b=k2d,则 (aqb)=(k1k2q)d,必然整除 d

  1. gcd(a,b)lcm(a,b)=ab

欧拉函数

  1. φ(n) 定义:1N 中与 N 互质的数的个数。
  2. 根据唯一分解定理 N=p1c1p2c2...pmcmφ(N)=N×i=1m(11pi)

证明:

对于每个质因子 p1N 中均有 Np 个倍数。

对于每几个质因子(这里举两个) pi,pjpipj 的倍数被减了两次,需要加回来一次,即 NN>piNpj+Npipj=N(11pi)(11pj)(容斥原理)


  1. 欧拉函数性质(省略版)

(1). n>11n 中与 n 互质的数的和为 nφ(n)/2

(2). 欧拉函数是积性函数。积性函数性质:f(ab)=f(a)f(b)a,b互质。

(3). 若 p 是质数,pn,p2n,则 φ(n)=φ(n/p)p

(4). 若 p 是质数,pn,p2n,则 φ(n)=φ(n/p)(p1)

tip:φ(p)=p1p 为质数(除了 p 的倍数,肯定 1p1 都与 p 互质)。

证明:

(1). 设 xn 互质,又 gcd(n,x)=gcd(n,nx)=1 所以与 n 互质的数 x,nx 成对出现,平均值为 n/>2,有 φ(n) 个。

(2). 请自行用公式计算证明。

(3). 设 n=kp2,则 n/p=kp,显然 n,n/p 质因子相同,代入公式算即可。

(4). p2npn/p,故 n/p 不是 p 倍数,p 又为质数,二者肯定互质,利用积性函数性计算即可。

tip:根据(3),(4),可以线性求欧拉函数(很简单,直接手推一波啥事没有)。

欧拉定理及扩展

  1. 若正整数 a,n 互质,则 aφ(n)1(modn)。(n 为质数你猜猜是啥)
  2. 若正整数 a,n 互质,则对于任意正整数 b,有 ababmodφ(n)(modn)
  3. a,n 不一定互质,且 b>φ(n),有 ababmodφ(n)+φ(n)(modn)

tip : 是费马小定理。


证明:

(1)先引入两个概念,同余类和剩余系。

对于 a[0,m1],集合 {a+km}k 为整数)的所有数模 m 余数都是 a,则称该集合为一个模 m 的同余类,简记为 a

m 的完全剩余系:一共有 m 个,分别为 0,1,2,...,m1

m 的简化剩余系:一共有 φ(m) 个,即 1m 中与 m 互质的个数。

例如:模 10 的简化剩余系为 {1,3,7,9}

此外,简化剩余系关于模 m 乘法封闭(集合内两个数相乘,还是在集合内)。

因为 a,bm 互质,则 a×b 也与 m 互质,a×bmodm 也与 m 互质。

(2) 接下来证明欧拉定理。

n 的简化剩余系为 {a1,a2,...,aφ(n)}

{aa1,aa2,...,aaφ(n)} 也可表示为 n 的简化剩余系。(A)

证明(A):

由于 a,n互质和简化剩余系的乘法封闭,故 aai 也在简化剩余系集合中。我们假设 aaiaaj(modn), ij,根据同余性质,两边同时除以 a 仍然成立,再根据 ai,aj<n,故 ai=aj。在简化剩余系中,显然不存在 ai=aj,故原结论成立。

证毕 (A)

综上: aφ(n)a1a2...aφ(n)(aa1)(aa2)...(aaφ(n))a1a2...aφ(n)(modn)

因此 aφ(n)1(modn)


证明:

b=qφ(n)+r,0r<φ(n)

abaqφ(n)+r(aφ(n))qar1arabmodφ(n)(modn)

tip :注意第一条定理,和 r 等于什么


证明:

先引入一个结论:若 n1,n2 互质,xy(modn1),xy(modn2),则xy(modn1n2)

这个移项后发现 xylcm(n1,n2) 的倍数,然后 lcm(n1,n2)=n1n2,故成立。

受这个启发,我们先把 n 质因数分解为 n=p1c1p2c2...pmcm。显然,任两个 pici 互质。如此,我们只需要证明  ababmodφ(n)+φ(n)(modpici)
,再把它们乘起来,原式得证。

接下来,我们分析如何证上面的式子。

gcd(a,pici)=1,显然符合欧拉定理 1,2 条结论,故可以证明,这里不多赘述。

gcd(a,pici)1,则可设 a=kpi。(显然,api 的倍数,毕竟 pi 是质数嘛)

由于 φ(pici)=picipicipi=pici1(pi1)pici12ci1ci

所以 bφ(n)φ(pici)ci

因此 pici ab abmodφ(n)+φ(n) 的因数,余数均为 0,故得证。

tip 1:关于 φ(pici) 的求法,我们考虑只有 pi 的倍数和 pici 不互质,减去即可。

tip 2:不等式里"忽视" p(i1) 和把 pi 放缩为 2 的理由:p 是质数,大于等于 2。

tip 3:如果不理解因数,注意 a=kpi,结合不等式,次方后必定是包含 pici 的。


扩展欧几里得

  1. 裴蜀定理:对于任意整数 a,b,存在一对整数 x,y,满足 ax+by=gcd(a,b)

证明:

  • 根据 gcd(a,b)=gcd(b,amodb),在最后 b=0 时,显然有 x=1,y=0 满足 a1+b0=gcd(a,0)
  • 设当前 ax+by=gcd(a,b)=bx+(amodb)y=bx+(aba/b)y=ay+b(xa/by)
  • x=y,y=xa/by。如此递归便能构造。
  1. 此过程还算了上述方程的一组特解,该算法为扩展欧几里得算法。代码实现:
void exgcd(int a,int b,int &x,int &y,int &d)
{
    if(b==0) return x=1,y=0,d=a,void();
    exgcd(b,a%b,x,y,d);
    int t=x;x=y,y=t-a/b*y;
}
  1. ax+by=c 的整数解。
  • 先判是否有解。设 d=gcd(a,b),有解当且仅当 dc
  • 特解:用 exgcd 求出方程 ax+by=d 的特解 x0,y0,再同时乘上 c/d,就得到原方程的特解 x1,y1
  • 通解:x=x1+bdk,y=y1kad,kZ

过程:假设将 x1 扩大为 x1+p观察法发现 y1 需要减小,设减小为 y1q。(p,qN+,事实上是我们希望 p,q 为正整数)

{ax1+by1=ca(x1+p)+b(y1q)=c

得:p=bqa,q=apb,又 p,qN+,所以 abq,bap

p,q 最小(也就是最小偏差量),就可以表示通解了。不难得出 bqmin=lcm(a,b)=abdp 同理,得 pmin=bd,qmin=ad

  1. 求解线性同余方程 axb(modn)

移项得 axb0(modn),即 axb 使 n 的倍数。假设为 y 倍,则 ax+ny=b。就是刚刚的不定方程。

同余数最短路

一般用来解决用若干整数使用任意次拼数这类的问题。

显然可以用完全背包来解决,复杂度 O(nV)V 为背包体积。

若用 dijkstra 求解最短路,设这若干整数绝对值最小的为 m,复杂度为 O(nlog(n+m))

假设我们用 a1,a2,...,an 拼数。

选定 a1 (可以选任意数)为模数。考虑 a1 的同余类 x,若其中一个数 t 能被 a2,a3,...,an 凑出,则 x 中大于等于 t 的数都能被凑出。

由此,考虑 a1 个同余类中最小能被凑出的数,设为 d,则此同余类在 [0,r] 中能凑出的数的个数为 rda1

问题转化为如何求每个同余类最小能被凑出的数。

这可以转化为一个图论问题。对每个同余类建一个节点,向其加 ai,i[2,n] 到达的同余类分别连边,边权为 ai。那么问题就转化为了跑最短路。

代码实现:

for(int i=0;i<a[1];i++)
	for(int j=2;j<=n;j++)
		G[i].emplace_back((i+a[j])%a[1],a[j]);
dijkstra();// spfa or other algorithms
// 假设要求统计能凑出 [l,r] 中多少不同的数。
ll ans=0;
for(int i=0;i<a[1];i++)
{
	if(r>=d[i]) ans+=(r-d[i])/a[1]+1;
	if(l-1>=d[i]) ans-=(l-1-d[i])/a[1]+1;
}

逆元

实用主义定义:考虑一个分数 abp 如何进行取模运算。就是乘上 b 关于 p 的逆元。

  1. O(logp) 求逆元:
  • 设逆元为 x,根据定义,abax。 即 xb1(modp)
  • p 为质数,根据费马小定理 bp11(modp),得 x=bp2
  • b,p 互质,则用 exgcd 求解上述线性同余方程(所以逆元可能不存在)。
  1. 线性求逆元:
  • 设当前要求 i 的逆元,p=qi+r,即 qi+r0(modp)
  • 同时乘 i1,r1qr1+i10(modp)
  • i1qr1(modp)
  • 为了方便,让逆元非负,右边加上 pr1(此数为 p 的倍数,模 p 意义下显然允许),得最终式子:
  • i1(pp/i)(pmodi)1
inv[1]=1;
for(int i=2;i<=n;i++) inv[i]=1ll*(p-p/i)*inv[p%i]%p;
  1. 线性求阶乘逆元:
  • (n!)1((n+1)!)1(n+1)(modp)
inv[n]=qpow(fac[n],p-2);
for(int i=n-1;~i;i--) inv[i]=1ll*inv[i+1]*(i+1)%p; 

中国剩余定理及扩展

p1,p2,...,pn 是两两互质的整数,p=i=1npi,Pi=p/piti 是线性同余方程 Piti1(modpi) 的一个解,则对于方程组

{xa1(modp1)xa2(modp2)...xan(modpn)

有整数解 x=i=1naiPiti。通解:x+kp(kZ)

证明:Pi 是除 pi 之外所有模数的倍数,那么最终 x 加上 Pi 的倍数就不影响其余方程。又因为 aiPitiai(modpi),满足当前方程组,所以代入 x,原方程组成立。

若要求最小正整数解,只需每步对 ans=(ansmodp+p)modp,使 ans 落在 0p1 范围内,就是最小正整数解。

代码实现:

void CRT()
{
	ll p=1,ans=0;
	for(int i=1;i<=n;i++) p*=p[i];
	for(int i=1;i<=n;i++)
	{
		int P=p/p[i],t,y;
		exgcd(P,p[i],t,y);
		ans+=1ll*a[i]*P*t;
	}
}

题外话

机房大佬叶师傅跟我提到过可以用 CRT 还原被取模数。

但我懒,没试过。

扩展中国剩余定理

考虑 pi 不两两互质的情况。

假设已经求出前 i1 个方程组的一个解 x。记 m=lcm(p1,p2,...,pi1),则 x+km(kZ) 是前 i1 个方程组的通解。

现在要满足 x+kmai(modpi),即 kmaix(modpi),其中 k 是未知量。这是一个线性同余方程,可用 exgcd 求解或判断无解。

那么前 i 个方程组的解就是 x+km

取模技巧:

  1. 在解上述非线性同余方程时,让 aixpi 取模,并变为正数。
  2. 求出新的 x 解后,对新的 m 取模。

代码实现:

void exgcd(int a,int b,int &x,int &y,int &d)
{
	if(!b) return x=1,y=0,d=a,void();
	exgcd(b,a%b,x,y,d);
	int t=x;x=y,y=x-a/b*y;
}

void EXCRT()
{
	int m=1;
	for(int i=1;i<=n;i++)
	{
		cin>>a>>p;
		int k,y,d;//d=gcd(m,p)
		a=((a-x)%p+p)%p;//取个模
		exgcd(m,p,k,y,d);
		if(a%d!=0) return puts("No solution"),void();//判断无解
		k=k*a/d;
		x+=k*m;
		m=lcm(m,p);
		x=(x%m+m)%m;
	}
}

高次同余方程

BSGS 算法及扩展

是用来解决形如 axb(modp) 这类的问题。

若保证 a,p 互质,便很容易想到欧拉定理。aφ(p)1(modp) 也就说明 ax 在模 p 意义下有一个长为 φ(p) 的循环节。

那么得到一种朴素算法:从 1φ(p) 枚举 x。由于 p 是质数,复杂度为 O(p)

BSGS 是对此算法的改进。其实不难想到分块。

x=itj(设为减号为了方便),则 (at)ibaj(modp)

t=φ(p),不难得出 O(p) 的算法。

bajmodp 所有可能取值放到一个哈希表里,再枚举 i 验证 (at)i 是否在表里就可以了。

为方便,直接取 t=p+1,注意要把 [1,p1] 里所有值取完,所以 i,j 范围为 [1,t]。(也可以是其他的,合理即可)

代码实现:

int BSGS()
{
    unordered_map<int,int> mp; //相当于哈希表
    int t=sqrt(p)+1,A=1;
    for(int i=1;i<=t;i++)
        A=A*a%p,mp[b*A%p]=i;
    int now=A;//now=a^t
    for(int i=1;i<=t;i++)
    {
        auto it=mp.find(now);
        if(it!=mp.end()) return i*t-it->second;
        now=now*A%p;
    }
    return -1;
}

a,p 不互质,我们想办法让其互质。 axb(modp) 可写为 aax1+py=b

根据裴蜀定理,当 d=gcd(a,p)b 时,方程有整数解。那么方程两边同时除以 d,得到 aax1d+pyd=bd;也就是 adax1bd(modpd)。这时,如果 d=1,那么就可以用 BSGS 了,否则继续递归。当然左侧多了一个系数,在 BSGS 时乘上即可。

注意当 x=0x1 变为负数,故需要特判。即当 b=1 时直接返回 x=0

细节很多,上代码:

//k 是系数,注意 BSGS 时也要传
int exBSGS(int a,int b,int p,int k=1)
{
    a%=p,b%=p;
    if(b==1||p==1) return 0;
    int d;
    for(int i=0;;i++)
    {
        d=gcd(a,p);
        if(b%d) return -1;
        b/=d,p/=d;
        k=k*a/d%p;
        if(k==b) return i+1;
        if(d==1) return BSGS(a,b,p,k)+i+1;
    }
}

原根

咕咕咕。

高斯消元

说人话就是解线性方程组。

可参考解二元一次方程组的过程。

直接说做法,可自行模拟。

每个未知数选择一个方程组,用选择的方程组消去其余方程组的当前未知数。

最后每个未知数等于选择的方程组的最终常数。

给一个过程方便理解。

如以下方程组:

{k1,1x1+k1,2x2+k1,3x3=d1k2,1x1+k2,2x2+k2,3x3=d2k3,1x1+k3,2x2+k3,3x3=d3

先消去第一个未知数:

{k1,1x1+k1,2x2+k1,3x3=d10x1+k2,2x2+k2,3x3=d20x1+k3,2x2+k3,3x3=d3

再消去第二个未知数:

{k1,1x1+0x2+k1,3x3=d10x1+k2,2x2+k2,3x3=d20x1+0x2+k3,3x3=d3

再消去第三个未知数:

{k1,1x1+0x2+0x3=d10x1+k2,2x2+0x3=d20x1+0x2+k3,3x3=d3

每个未知数的值显而易见了。

如果出现 0=d 这样的方程:

d=0,则有多个解,当前未知数称为自由元。

d0,则方程组无解。

那么为了方便,每次找到对于当前未知数含有最大系数的方程组,如果系数 =0,则判断无解。

其实这么说还是很抽象。直接看代码。

void gauss(vector<vector<double>> &a)
{
	//读入系数,a[i][n+1] 是等号后面的常数
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n+1;j++)
			a[i][j]=rd();
	for(int i=1;i<=n;i++)
	{
		//现在考虑第 i 个未知数
		//寻找对于第 i 个未知数具有最大系数的方程组
		int mx=i;
		//注意前 i-1 个方程组已使用
		for(int j=i+1;j<=n;j++)
			if(fabs(a[j][i]-a[mx][i])>eps) mx=i;
		if(fabs(a[mx][i])<eps) {puts("No solution");return;}//判断无解
		swap(a[i],a[mx]);
		for(int j=1;j<=n;j++)//枚举每个方程组
			if(i!=j)
				for(int k=i+1;k<=n+1;k++)
					a[j][k]-=a[j][i]/a[i][i]*a[i][k];	
	}
	for(int i=1;i<=n;i++) a[i][n+1]/=a[i][i];
}

还有一种遇的比较多的,异或方程组。就是加号变成了异或运算。

那么此时的系数就为 01。加法减法就变为了异或,且不需要乘法。

为方便,可以把每个方程组进行状态压缩。及用一个整数表示。

如果位数过多,存不下,考虑用 bitset 优化常数。

void gauss()
{
	for(int i=1;i<=n;i++)
	{
		//变化一下思路,找最大的 a[i],也就是主元位数最高的 a[i]
		for(int j=i+1;j<=n;j++)
			if(a[j]>a[i]) swap(a[i],a[j]);
		if(a[i]==1) {puts("No solution");return;}//出现 0=1 的方程,无解
		for(int k=n;k;k--)
			if(a[i]>>k&1)//以 a[i] 最高位为主元,消去其他方程该位的系数
			{
				for(int j=1;j<=n;j++)
					if(i!=j&&(a[j]>>k&1)) a[j]^=a[i];
				break;
			}
	}
}

本文作者:spider_oyster

本文链接:https://www.cnblogs.com/spider-oyster/p/17494945.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

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