蒟蒻的数论笔记

数论笔记

定义



一些规定

1.如无特殊标记,fk(n)=fk(n)

2.如无特殊说明,num(n,p)=max(kpk|n)

3.[x]按照语境通常是向下取整的含义

4.p表示容易质数,P表示质数集,如无特殊说明,pi表示第i大的质数


许多数论函数

欧拉函数φ(x):[1,n]中与n互质的数的个数

φ(n)=nΠpi1pi

刘维尔函数

λ(n)=dP,d|n1

莫比乌斯函数

μ(n)={μ(n)=1n=1μ(n)=0a2[a1]|nμ(n)=(1)λ(n)otherwise

约数个数函数

d(n)=d|n1

元函数

ϵ(n)=[n=1]

恒等函数

I(n)=1

单位函数

id(n)=n

约数和函数

σ(n)=d|nd

积性函数:

(a,b)=1,f(ab)=f(a)f(b)

完全积性函数:

a,bZ+,f(ab)=f(a)f(b)



狄利克雷卷积

定义

f,g为积性函数
fg=d|nf(d)g(nd)


狄利克雷卷积的一些性质

fϵ=f
fg=gf
f(gh)=(fg)h
(f+g)h=fh+gh



定理

小知识

常见结论

神秘小知识

(1)

d|nφ(d)=n

证明如下:

构造一个二元数列(h,k)使得(h,k)=1kn1h<k,特殊地,(1,1)也被认为是合法的

构造另一个二元数列(i,n)其中1in,

对于(h,k)考虑其到(i,n)的映射,

不难发现(h,k)(hnk,n)

h<kknhk<1hnk<n(i,n)=(hnk,n)

可以得到|(h,k)||(i,n)|

再考虑(i,n)(h,k)的映射

(i,n)(i(i,n),n(i,n))n(i,n)n(h,k)=(i(i,n),n(i,n))

可以发现|(i,n)||(h,k)|

放缩得|(i,n)|=|(h,k)|

显然有|(i,n)|=n,所以|(h,k)|=n

结合定义知k=d(h,k)=φ(d),即|(h,k)|=dnφ(d)

得证

(2)

Iμ=ϵ

证明如下

n=1,Iμ=μ(1)=1

n1n=piai

μ的定义知,若num(m,p)>1μ(m)=0

故而我们只考虑i=1λ(n)[num(m,pi)1]=1m,下面的m都满足这一性质

Si=λ(d)=iμ(d)

Li=|Si|

i1(mod2)Si=Li

i0(mod2)Si=Li

Si=(1)iLi

就是说Si考虑有i个质因子的m的总贡献,Li表示这样的m的个数,特殊地,我们有L0=S0=1,也就是m=1的贡献

由组合数的知识容易得到

Li=(λ(n)i)

考虑用Li来表达Iμ,简单带入可得

Iμ=d|nμ(d)=i=1λ(n)[num(m,pi)1]=1μ(m)=i=0λ(n)Si=i=0λ(n)(1)iLi=i=0λ(n)(1)i(λ(n)i)

考虑构造函数

f(x)=i=0(λ(n)i)xi=(1+x)λ(n)

f(1)=i=0λ(n)(1)i(λ(n)i)=0

所以有

n1,Iμ=0

综上,得证

(3)

gcd(n,m)=1φ(nm)=φ(n)φ(m)也就是说欧拉函数是积性函数,但不是完全积性函数

(4)

杜教筛的构造

要求f的前缀和,构造一个很好求前缀和的函数h和一个很好求前缀和的辅助函数g,使得

h=fg

S(n)=i=1nf(i),Sh(n)=i=1nh(i)

按狄利克雷卷积的定义展开

Sh(n)=i=1nd|ig(d)f(id)

考虑用Sh表示S,于是考虑交换求和符号,使得gf分离:

i=1nd|ig(d)f(id)=d=1ng(d)i=1[nd]f(i)=d=1ng(d)S([nd])

发现n1=n,所以我们把右边第一项提出来:

Sh(n)=g(1)S(n)+d=2ng(d)S(nd)

整理得

S(n)=Sh(n)d=2ng(d)S(nd)g(1)

由于构造的Sh(n)是容易求得的,即可以在低于线性的时间内求出(实际上很多时候都是Θ(1)的),

g(1)也显然可以在Θ(1)的时间里求出,所以只需要在低于线性的时间里求g(d)S(nd)的除去第一项的前缀和

考虑使用整除分块,则转化为求g在不超过2n段上的和,前缀和即可,

对于S([nd])项,我们直接递归,暴力地预处理出n23个前缀和即可

复杂度分析

下文考虑中均认为求f,g的前缀和是Θ(1)

运用杜教筛,我们把求S(n)转化为了求所有的S(nd),本质上只有2n种取值,忽略常数后,我们设杜教筛的复杂度为T(n),可得方程

T(n)=n+i=2[n](T(i)+T(ni))

含义为,求前n项前缀和的复杂度是整除分块的复杂度+处理出每个S(nd)S(d)的复杂度

这里我们默认了n超出了预处理的前缀和,但nd显然可能是被预处理过的值,所以需要考虑预处理,设预处理了k(kn)个,那么总的复杂度为:

k+T(n)=k+n+i=2[n](T(i)+T(ni))

注意到所有T(i)都已经被预处理了,可以Θ(1)得到,复杂度为Θ(n)

接着考虑T(ni)的处理,考虑继续展开,注意此时不需要再预处理了:

k+T(n)=k+n+i=2nT(ni)=k+n+i=2nknk

上式最后的含义为一个大于k的值迭代到k以内的迭代次数

令总复杂度为D=k+T(n),选k为主元,考虑其极值,得

D=1n2k32=0

2k32=n

k=(n2)23

最终的复杂度为Θ(n23).

(5)

莫比乌斯反演

F(n)=d|nf(d)f(n)=d|nμ(d)·F(nd)

证明如下

F=fI

Fμ=fIμ

Fμ=f

得证

积性函数

(6)

对于任意积性函数f

n=piai

f(n)=f(piai)

有算数基本定理和积性函数的定义容易推出

(7)对于任意完全积性函数f

f(nk)=fk(n)

若积性函数f满足上式,则f也是完全积性函数

(8)

积性函数的神秘性质

d|nf(d)=i=1λ(n)j=0num(n,pi)f(pij)

由算数基本定理容易推出

(9)

由结论1得:

φI=id

考虑消去其中的I,利用结论2,两边同卷μ

φIμ=idμφ=idμφ(n)=d|nμ(d)ndφ(n)n=d|nμ(d)d

Powerful Number 筛

个人觉得比上面两种筛法都简单

先定义powerful number为n,pPpn,p2n

说人话就是所有质因子次数不小于2的数

还是求积性函数的前缀和,构造函数g,g(p)=f(p),并使得g的前缀和容易求出

f=gh

f(p)=g(1)h(p)+g(p)h(1)=h(p)+g(p)h(p)=0

考虑展开卷积,得

S(n)=i=1nf(i)=i=1ndig(d)h(id)

考虑g的前缀和是容易求出的,交换求和号

S(n)=i=1nh(i)dnig(d)=i=1nh(i)Sg(ni)

这依然是Θ(n)的,但因为h是积性函数并且h(p)=0,所以h(n)0,nPowerful Number

考虑Powerful Number的数量,以下的qPowerful Number

由定义知

q=i=1λ(q)pixii,xi2

上文的pi仅表示q的质因子

不难发现

i,xi=2ai+3bi(ai,bi0)

那么

q=i=1λ(q)(piai)2(pibi)3q=A2B3

power(n)=[nPowerful Number],那么powerful number的数目就是power()的前缀和

考虑枚举A,计算对于的贡献,为了不算重,应该只枚举1n,那么

Spower(n)=i=1npower(i)=x=1ni=1n[ix23Z]1nnx23dx=32(nn13)=Θ(n)

那么就只需要求出[1,n]中的所有powerful number,然后就可以Θ(Fn)求出f的前缀和,其中F是求g的前缀和的复杂度

接下来介绍一个小技巧:

观察f=gh,发现fg1=ϵh=h,其中g1g=ϵ

所以只要构造出g就可以求出h,减小了构造的工作量

powerful number筛的时间复杂度和思维难度都有一定优势,但其辅助函数难以构造,使得很多问题不能用其解决


真定理

1.欧拉定理:

akakmodφ(p)+φ(p)(modp)(k>φ(p))

特殊地,gcd(a,p)=1时,有akakmodφ(p)(modp)

注意,使用时应当判断kφ(p)的大小

2.费马小定理

其实就是欧拉函数的性质在欧拉定理中的应用:

λ(p)=1

ap1=1(modp)


代码

封装好了一些基本的东西

卢卡斯定理没有过

class MyMath
{
	public:
		vector<int> prime;
		vector<int> phi;
		vector<int> mu;
		template<typename Tp_> inline Tp_ inv(Tp_ x,Tp_ p) {return pow(x,-1,p);}
		inline bool is_prime(ll x)
		{
			if(x<0) x=-x;
			if(!prime.empty())
				if((*prime.rbegin())*(*prime.rbegin())>=x)
				{
					for(vector<int>::iterator it=prime.begin();(*it)*(*it)<=x&&it!=prime.end();++it)
						if(!(x%(*it))) return false;
					return true;
				}//surely a bit faster,almost log(n)
			for(ll i(2);i*i<=x;++i)
				if(!(x%i)) return false;
			return true;
		}//n^0.5 hardly used
		inline ll pow(ll a,ll x,ll p)
		{
			if(x==0) return 1;
			if(x>0)
			{
				ll ret=1;
				while(x) ret=(x&1?a*ret%p:ret),x>>=1,a=a*a%p;
				return ret;
			}
			else
			{
				if(!phi.empty())
					if(phi.size()>p)
						return pow(a,x%phi[p]+phi[p],p);
				ll ph=Phi(p);
				return pow(a,x%ph+ph,p);
			}
		}//maybe log,n^0.5 when using Phi()
		template <typename Tp_> inline Tp_ gcd(Tp_ x,Tp_ y)
		{
			if(x>y) swap(x,y);
			if(x==0) return (y?y:1);
			return gcd(y%x,x);
		}//almost log
		template <typename Tp_> inline Tp_ lcm(Tp_ x,Tp_ y) {return x/gcd(x,y)*y;}
		inline int ex_gcd(int a,int b,int c,int &p,int &q)
		{
			int g=ExEuclid(a,b,p,q);
			if(c%g) return p=0,q=0;
			g=c/g,p*=g,q*=g;
			return 1;
		}//a*p+b*q=c(mod p),about log,maybe
		template<typename Tp_> inline Tp_ Phi(Tp_ x)
		{
			Tp_ tmp=x,ret=x;
		    for (Tp_ i=2;i*i<=x;++i)
		        if (tmp%i==0)
		        {
		            ret=ret-ret/i;
		            while (tmp%i==0) tmp/=i;
		        }
		    if(tmp>1) ret-=ret/tmp;
			return ret;
		}//n^0.5
		template<typename Tp_> inline Tp_ Lucas(Tp_ n,Tp_ m,Tp_ p)
		{
			if(m==0) return 1;
			Tp_ np=n%p,mp=m%p;
			if(np<mp) return 0;
			mp=min(np-mp,mp);
			Tp_ p1=1,p2=1;
			for( Tp_ i = 0 ; i < mp ; ++i )
				p1=p1*(n-i)%p,
				p2=p2*(i+1)%p;
			return (p1*pow(p2,p-2)%p)*Lucas(n/p,m/p,p)%p;
		}//p must be a prime
		inline void sieve(const int capN)
		{
			bool *isp;
			isp=new bool [capN+5];
			memset(isp,0,sizeof(bool)*(capN+4));
			if(!prime.empty()) prime.clear();
			for(int i(2);i<=capN;++i)
			{
				if(!isp[i]) prime.emplace_back(i);
				for(vector<int>::iterator it=prime.begin();it!=prime.end();++it)
				{
					if(i*(*it)>capN) break;
					isp[i*(*it)]=1;
					if(!(i%(*it))) break;
				}
			}
			delete isp;
		}//O(n) and need more place
		inline void phi_sieve(const int capN)
		{
			if(!prime.empty()) prime.clear();
			phi.resize(capN+4,0),phi[1]=1;
			for(int i(2);i<=capN;++i)
			{
				if(!phi[i])
					prime.emplace_back(i),
					phi[i]=i-1;
				for(vector<int>::iterator it=prime.begin();it!=prime.end();++it)
				{
					if(i*(*it)>capN) break;
					if(!(i%(*it)))
					{
						phi[i*(*it)]=phi[i]*(*it);
						break;
					}
					else phi[i*(*it)]=phi[i]*(*it-1);
				}
			}
		}//cna get phi while finding primes in [1,capN]
	private:
		inline int ExEuclid(int a,int b,int &p,int &q)
		{
			if(b==0) return p=1,q=0,a;
			int d=ExEuclid(b,a%b,p,q);
			int tmp=p;
			p=q,q=tmp-a/b*q;
			return d;
		}//a help function of ex_gcd
}M;
posted @   嘉年华_efX  阅读(356)  评论(1编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示

目录导航