ことばがありあまれどなお、 このゆめはつづいてく|

trsins

园龄:3年10个月粉丝:18关注:2

【学习笔记】数论杂谈

一. 素数相关

0. 约定

若无特殊说明,本部分做以下记号规定。

pPP 为质数集。

π(n) 表示 1n 内的素数个数。

P+(n),P(n) 分别表示 n 的最大/最小质因子。

νi 表示 i 的可重质因子个数。

1. 素数定理

π(x)xlnx(x+)

0<a<b,s.t.axlnx<π(x)<bxlnx

常用于估算素数数组大小。

2. 筛法

埃氏筛不展开。

欧拉筛保证合数只被筛一次,复杂度 O(n)。即线性筛。

同时通过欧拉筛容易实现对于欧拉函数,莫比乌斯函数的预处理。

3. 判断素数

  1. 暴力。

暴力的 O(n) 是一种准确素性测试算法的复杂度。

  1. Miller-Rabin 算法

Miller-Rabin 使用费马小定理和二次探测定理进行素性测试。

显然根据费小和二次探测有

ap11(modp)

x21(modp),x±1(modp)

设需要检验素性的数为 x。取 y<x,那若 x 为质数则 yx11(modx)

再令 x1=t×2k

根据二次探测定理,考虑对 yt×2k 不断开方,有

0k<k,yt×2k1(modx)

yt1(modx)

我们如此地对于 x 进行 m 次测试,复杂度是 O(mlog3x)。但当然这是个概率算法。

一组常用的底数是 {2,3,7,61,24251}。在 1016 内仅 46856248255981 发生误判。

#define int long long
int base[15]={0,2,3,7,61,24251};
inline bool Miller_Rabin(int n){
	if(n<2||n==46856248255981ll)return false;
	int t=n-1,k=0;
	while(t%2==0){
		t>>=1;
		k++;
	}
	for(int i=1;i<=5&&base[i]<n;i++){
		int x=qpow(base[i],t);
		if(x==1)continue;
		for(int j=1;j<k;j++)if(x!=n-1)x=(x*x)%n;
		if(x!=n-1)return false;
	}
	return true;
}

4. Meissel-Lehmer 算法求 π(n)

以下令 pi 表示第 i 个素数。p1=2

定义 ψi(n)=1x<n[P(x)>pi]τi(n,k)=1x<n([P(x)>pi]×[νx=k])

由于容斥显然有

ψi(n)=k0τi(n,k)[pikn]

其中钦定 τi(n,0)=1

考虑对于 n13mn12,其中 m=pω

那么对于 k3 时有 τω(n,1)=π(n)ωτω(n,k)=0

π(n)=τω(n,1)+ω=ψω(n)τω(n,2)τω(n,0)+ω

那么对于 π(n) 我们只需计算 ψω(n)τω(n,2) 即可。

  1. 计算 τω(n,2)

考虑它的实际意义:即质数对 (p,q) 满足 m<pqpqn 的个数。

显然 p[m+1,n],那么亦有 q[p,np],可得:

τω(n,2)=p[m+1,n](π(np)π(p1))

此时线性筛预处理容易做到 O(nm)

  1. 计算 ψω(n)

通过容斥有

ψω(n)=ψω1(n)ψω1(npω)

边界 ψ0(n)=[n]

那么考虑 dfs 计算此式,此时复杂度难以接受。考虑对于边界加入 xm 的剪枝。

ψω(n)=xmμ(x)[nx]+xP(x)m<xμ(x)ψπ(P(x))1(nx)

R=xmμ(x)[nx]

T=xP(x)m<xμ(x)ψπ(P(x))1(nx)

ψω(n)=R+T

显然 R 容易预处理。

对于

T=pmxm<xp[P(x)>p]μ(x)ψπ(p)1(nxp)

考虑分块计算,令

T1=n13<pmxm<xp[P(x)>p]μ(x)ψπ(p)1(nxp)

T2=n14<pn13xm<xp[P(x)>p]μ(x)ψπ(p)1(nxp)

T3=pn14xm<xp[P(x)>p]μ(x)ψπ(p)1(nxp)

T=T1+T2+T3

首先 T3 显然通过树状数组枚举 p 统计对 ψ 的贡献在 O(nmlogn)
简单实现。

对于 T1,T2 由于 n14<p<P(x),xmn,故必有 xP

T1=n13<pmp<qmψπ(p)1(npq)

T2=n14<pn13p<qmψπ(p)1(npq)

考虑计算 T1,T2

T1 由于 npq<n13<pψπ(p)1(npq)=1,所以 T1 容易 O(m) 计算:

T1=n13<pm(π(m)π(p))

对于 T2 再次拆开为 S1+S2=T2

S1=n14<pn13p<qm[qnp2]ψπ(p)1(npq)

S2=n14<pn13p<qm[q>np2]ψπ(p)1(npq)

对于 S1 由于有 pnpq<n<p2,故有 ψπ(p)1(npq)=π(npq)π(p)+1

然后你再分为 S1=Q1+Q2

Q1=n14<pn13(1π(p))p<qm[qnp2]

Q2=n14<pn13p<qm[qnp2]π(npq)

我们发现 Q1 简单处理即可,对于 Q2 进行类数论分块只有 π(np2) 块,所以保证 O(n23logn)

对于 S2,有 nmnq<pnpq<pψπ(p)1(npq)=1

S2=nm<pn13(π(m)π(min{p,np2}))

复杂度 O(m)

考虑整个过程的时间复杂度计算。令 m=O(n13log2n) 时复杂度最优为 O(n23logn)

若将 Q2 分为五个部分计算(参见oi-wiki),可优化至 O(n23log2n)

常数极大的代码。比最优解慢了近40s。


inline void init(){
	Getprime();
	sz[0]=1;
	for(int i=0;i<S;i++)phi[i][0]=i;
	for(int i=1;i<=2;i++){
		sz[i]=p[i]*sz[i-1];
		for(int j=1;j<S;j++)phi[j][i]=phi[j][i-1]-phi[j/p[i]][i-1];
	}
	return;
}

inline int Phi(int x,int id){
	int P=p[id],r=sz[id];
	if(!id)return x;
	if(id<=2)return phi[x%r][id]+(x/r)*phi[r][id];
	if(x<=P*P)return pi[x]-id+1;
	if(x<=P*P*P&&x<N){
		int y=sqrt2(x),t=pi[y],res=pi[x]-((t-id+1)*(t+id-2)>>1);
		for(int i=id+1;i<=t;i++)res+=pi[x/p[i]];
		return res;
	}
	return Phi(x,id-1)-Phi(x/p[id],id-1);
}

inline int Pi(int x){
	if(x<N)return pi[x];
	int X=Pi(sqrt2(x)),Y=Pi(sqrt2(sqrt2(x))),Z=Pi(sqrt3(x)),res=Phi(x,Y)+((X+Y-2)*(X-Y+1)>>1);
	for(int i=Y+1;i<=X;i++){
		int t=x/p[i];res-=Pi(t);
		if(i>Z)continue;
		int O=Pi(sqrt2(t));
		for(int j=i;j<=O;j++)res-=Pi(t/p[j])-j+1;
	}
	return res;
}

5. Pollard-Rho 算法计算非平凡因子

对于寻找合数 n 的一种非平凡因子的朴素做法显然可以枚举 [1,n] 来做到。当然你还可以通过预处理一点打表来达到 O(nlnn)

生日悖论是一个著名的问题。

钦定有 n 个人,一年 m=365 天。

那么 n 个人中没有任意两人生日相同的概率为

P=i=1nmi+1m

那么反之存在二人生日相同即为 1P

1+xex

Pi=1nei1m=en(n1)2m

而著名的悖论在于带入 n=23 即有 P50%。由于反直觉被认为是悖论。

事实上在 n=mln2 左右时有 P=50%

考虑一个随机序列 Xf(x)=x2+c(modn) 生成。c 随机。

那么这个序列 X 在出现 xi=xj,i<j 时,X 就从 i 开始陷入循环。

即如上图所示,因其形像 ρ 而成为 rho

那么这样的“伪随机”序列 X 产生环的期望其上述的生日悖论。

mn 的最小非平凡因子。显然 mn,再记 yi=xi(modm)

yi+1=xi+1(modm)=xi2+c(modm)=(ximodm)2+c(modm)=yi2+c(modm)

那么对于新的序列 Y,若存在 i,j 使 xixjyi=yj,即 m|xixj|,n|xixj|.

那么令 g=gcd(|xixj|,n)g 必然是 n 的一个非平凡因子。

期望枚举 O(m) 次来获得 g。而 mn,故期望 O(n14)

考虑优化。

  1. Floyd 判环

即通过双指针 i,j,其中 i 的速度是 j 的两倍,以此去遍历环,那么 i,j 相遇时是环长整数倍。当然还是检查 g=gcd(|xixj|,n)。复杂度 O(n4logn)。这里不做赘述。

  1. 倍增优化

多次求 gcd 是时间复杂度的瓶颈,考虑如何优化。

每次将 |xixj| 相乘,若对于一对 xI,xJ,有 gm 的非平凡因子,那么设 S=|xixj|,必然有 g=gcd(S,n) 亦为 n 的非平凡因子。

那么考虑累计到一定样本再进行 gcd。考虑将阈值设为倍增,每次判断 g<n 即可。

6. 反素数

素数是因子只有两个的数,反素数就是因子最多的数而不是emirp,并且因子个数相同的时候值最小。显然反素数是相对于一个集合的。

考虑对于一个反素数 n=i=1mpiki,即有 i=1m(ki+1)max

考虑在给定范围内如何求反素数。

显然直接分解质因数无法接受。考虑反素数的性质。

  1. 反素数是从 2 开始的连续素数的幂次形式乘积。

显然。所以考虑 minm 使 i=1m+1>n,我们只需枚举前 m 个素数。

  1. 数值小的素数的幂次大于等于数值大的素数,即 k1k2km

还是显然。若有 ki<kj,i<j 则将 kjki 移至 pi 幂次上更优。那么最坏枚举幂次枚举至 INT_MAX 差不多 64 即可。

整个过程就容易用搜索实现。

CF27E。模板题。

inline void dfs(int x,int k,int m,int dep){
	if(dep>n||x<=0ll||x>=ans||k>16)return;
	if(dep==n){
		ans=x;
		return;
	}
	x*=p[k];
	for(int i=1;i<=m;i++,x*=p[k])dfs(x,k+1,i,dep*(i+1));
	return;
}

二. gcd 相关

1. 求 gcd

显然通过欧几里得/辗转相除容易在 O(logn) 时间内递归求出 gcd

inline int gcd(int x,int y){
	if(!y)return x;
	return gcd(y,x%y);
}

考虑一个问题:求一组可行解 (x,y) 满足 ax+by=gcd(a,b)

ax1+by1=gcd(a,b)bx2+(amodb)y2=gcd(b,amodb)=gcd(a,b)

ax1+by1=bx2+(amodb)y2=bx2+(abab)y2

x1=y2,y1=x2aby2

那么剩余的按照普通的欧几里得递归做就行。

inline int exgcd(int a,int b,int &x,int &y){
	if(!b){
		x=1,y=0;
		return a;
	}
	int res=exgcd(b,a%b,x,y),X=x;
	x=y,y=X-(a/b)*y;
	return res;
}

2. 裴蜀定理

a,bZ(a or b0),x,yZ,ax+by=gcd(a,b)

证明:

g=gcd(a,b),s=min(ax+by)N+,t=as,显然 x,yZg|ax+by

amods=at(ax+by)=a(1xt)+b(yt)

那么 amodsa,b 的线性组合。同时有 0amods<s。再由 s 定义易知 amods=0。同理亦有 s|b。故 sa,b 公约数。故 gs

又因为 g|a,g|bsa,b 线性组合。显然 g|s。那么即得 g=s

裴蜀定理的一个推论:a,b 互质的充要条件是存在 x,yZ 使 ax+by=1。此不做赘述。

那么这可看做是上述 exgcd 的一个补充。

3. 类欧几里得

虽然我并不知道这放在这个专题是否合适

本文作者:trsins

本文链接:https://www.cnblogs.com/trsins/p/17970748

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

posted @   trsins  阅读(36)  评论(0编辑  收藏  举报
历史上的今天:
2022-01-17 【做题记录】Ynoi2018 天降之物
2022-01-17 【学术】连分数
2022-01-17 【做题记录】Ynoi2015 盼君勿忘
2022-01-17 【做题记录】BJOI2016 水晶
2022-01-17 【做题记录】P4965 薇尔莉特的打字机
2022-01-17 【做题记录】POI2011 Lightning Conductor
2022-01-17 【做题记录】CF961G Partitions
点击右上角即可分享
微信分享提示