数论初步

参考资料#

《算法竞赛进阶指南》

洛谷日报

OI Wiki

基本数论概念#

整除、因数、倍数什么的就不用说了吧。

数论函数#

指定义域为正整数的函数。下面讨论的『函数』如非特殊说明,都指数论函数。

积性函数#

定义#

若有 gcd(a,b)=1,f(ab)=f(a)f(b),则称 f 为积性函数。
若有 a,b,f(ab)=f(a)f(b),则称 f 为完全积性函数。

性质#

f(x),g(x) 都是积性函数,则以下函数也是积性函数:

f(xp),fp(x),f(x)g(x),d|xf(d)g(xd)

前三个证明显然,第四个其实是这两个函数的狄利克雷卷积,后面会讲。

欧几里得算法#

内容及其证明#

求 gcd 的常用方法。其内容为:

gcd(a,b)=gcd(b,amodb)

证明如下:

a<b 时,定理显然成立。

ab 时,设 a=kb+r,其中 0r<b,即 r=amodb。因为 da,db 都有 dkb,所以 d(akb)dr。于是我们得到结论:da,db,d(amodb)。因此,bamodb 的公约数集相对于 ab 的公约数集是没有改变的,那么两个集合中最大的数自然不变。

证毕。

算法应用及其时间复杂度证明#

开始前,先来证明一个结论:当 ab 时,amodb<a2

注:证明过程中的 x 为任意不小于 1 的正实数。

x>x2(一个大于等于 1 的数的一半一定小于其整数部分)

ab>a2b

abb>a2

aabb<a2

amodb<a2

根据欧几里得算法,代码很容易得出:

int gcd(int a,int b){
	return b?gcd(b,a%b):a;
}

其时间复杂度为 O(loga+logb)。证明如下:

在求 gcd(a,b) 时,会出现下面两种情况:

  • a<b,此时会由 gcd(a,b) 递归到 gcd(b,a)
  • ab,此时会由 gcd(a,b) 递归到 gcd(b,a%b)。这样就使得其中一个数至少折半。因此这一情况最多发生 O(loga+logb) 次。

由于前者发生后一定会发生后者,所以前者的发生次数一定不多于后者,所以总的时间复杂度仍为 O(loga+logb)

欧拉函数#

内容#

欧拉函数为 φ(n),表示小于等于 n 的与 n 互质的数的个数。即:

φ(n)=i=1n[ni]

性质#

性质一#

若在算数基本定理中,n=i=1mpici,则:

φ(n)=ni=1m(pi1pi)

证明:容斥原理。设每个 n 的质因子 pi[1,n] 中的倍数集合为 Ai。则有:

φ(n)=n|i=1mAi|

又因为我们知道 |Ai|=npi,于是就可以用容斥的公式:

φ(n)=nk=1m(1)k11i1<i2<<ikm|j=1kAij|

我们还知道,|i=1kAi|=ni=1kpi。于是:

φ(n)=nk=1m(1)k11i1<i2<<ikmnj=1kpij=n(1k=1m(1)k11i1<i2<<ikmj=1k1pij)=n(1k=1m1(1)k12i1<i2<<ikmj=1k1pij1p1+k=1m(1)k11=i1<i2<<ikmj=1k1pij)=n(11p1)(1k=1m1(1)k12i1<i2<<ikmj=1k1pij)==ni=1m(11pi)

性质二#

欧拉函数是积性函数。

证明:(其中 gcd(a,b)=1,在唯一分解定理中,a=i=1npicib=i=1mqiki)。

φ(a)φ(b)=ai=1n(11pi)bi=1m(11qi)=φ(ab)

https://www.zhihu.com/question/410737433

性质三#

n>1,i=1ni[ni]=nφ(n)2

证明(运用了莫比乌斯反演,可以先把整篇文章看完再回过头来看):

i=1ni[ni]=i=1niε(gcd(n,i))=i=1nidndiμ(d)=dnμ(d)i=1ni[di]=dnμ(d)di=1ndi=12dnμ(d)d(nd+1)nd=n2dnμ(d)(nd+1)=n2((1μ)(n)+dnμ(d)id(nd))=n(ε(n)+φ(n))2

upd: 我太 naive 了。现在告诉大家一种更简单的证明方法。

这个式子其实在算不超过 n 的与 n 互质的数的和。我们知道 gcd(a,b)=gcd(b,ab),所以对于每个 in,都有 (ni)n,它们的和等于 n,所以事实上就是 φ(n)2n 相加。

埃氏筛与欧拉筛#

埃氏筛#

这种筛法虽然时间复杂度逊于欧拉筛,但是对于题目的一些特殊要求能够更好地应付。例如 筛一段区间内的质数 就只能用埃氏筛。

埃氏筛的代码如下:

for(ll i=2;i<=N;i++){
	if(!vis[i]){
		p.push_back(i);
		if(i*i<=N){
			for(ll j=i*i;j<=N;j+=i)vis[j]=1;
		}
	}
}

可以看出,埃氏筛是对于每个质数,把能整除它的所有和数全部标记一遍。但是一个小于 i2 的合数必定有比 i 更小的质因子,所以它肯定已经被筛过,没有必要再筛。

如果想要筛一段区间 [L,R] 内的质数,只需要用 [1,R] 内的质数去筛就可以了。

设需要筛出的质数范围为 [1,N],则其时间复杂度为 O(NloglogN),证明需要用微积分,然而我不会,会微积分的可以取 OI Wiki 看证明。 现在会了

欧拉筛#

现在我们想筛出 2N 的所有质数。

欧拉筛的核心思想是从大到小累计质数,以达到每个合数只筛一次的目的。算法用一个数组来记录每个数的最小质因子。设这个数组为 v,我们可以用以下方式维护它:

  • 从小到大用 i2N 的每个数扫一遍。
  • 如果 vi 没有被修改过,说明 i 没有被筛,所以 i 是质数,其最小质因子为其本身,即执行 vii
  • 无论 i 是不是质数,都需要在 vi 的基础上累计质因子。即扫描不大于 vi 的每个质数 pvpip

注意第三点,因为质因子是从大到小累积的,所以 p 一定是 pi 的最小质因子。

该算法从 2N 的每个数都只会被遍历一次,筛一次,所以时间复杂度为 O(N)

P3383 线性筛质数模板的核心代码:

void init(int N){
	for(int i=2;i<=N;i++){
		if(!v[i]){v[i]=i;p.push_back(i);}
		for(int pr:p){
			if(pr>v[i]||pr>N/i)break;
			v[pr*i]=pr;
		}
	}
}

线性筛欧拉函数#

线性筛过程中,假设我们正在用一个数 n 和一个质数 ppn,且已知 φ(n),需要求出 φ(pn)。设在唯一分解定理中,n=i=1mqici

  • pn 时,φ(pn)=pni=1m(qi1qi)=pφ(n)
  • pn 时,根据积性函数的定义,φ(pn)=φ(p)φ(n)

然后就能求出 [2,N] 中的每一个整数的欧拉函数值了。只需在上面的程序中稍加修改:

inline void init(int n){
	for(int i=2;i<=n;i++){
		if(!v[i]){v[i]=i;phi[i]=i-1;p.push_back(i);}
		for(int pr:p){
			if(pr>v[i]||pr>n/i)break;
			v[pr*i]=pr;
			phi[pr*i]=phi[i]*(pr==v[i]?pr:phi[pr]);
		}
	}
}

线性筛任意积性函数#

已知 f 是一个积性函数。设在唯一分解定理中,n=i=1mpici。则有:

f(n)=i=1mf(pci)

因此,只要在欧拉筛时,统计每个数最小质因数 p 及其次数 c,就可以递推计算积性函数:

f(n)=f(pc)f(npc)

以后,在讨论积性函数时,只讨论其在 pk 的取值然后相乘的方法,是比较常见的。

例题#

P5481 [BJOI2015] 糖果#

你可能会问这不是一道组合计数问题吗,为啥放到数论这里。

因为这道题的难点不在于计数,而在于任意模数。

计数式子插板法随便搞搞就得出来了,是

(m+k1k1)n_=((m+k1)m_m!)n_

所以任意模数怎么办呢?如果直接 exgcd 算很可能出现逆元不存在的情况,而且还不一定是模数的倍数,所以答案不一定是 0

然后我就忍不住看了题解,发现只要把 m! 分解质因数,然后把上面的式子除掉这些质因数就行了。

这里有一个分解阶乘质因数的技巧:枚举所有小于等于 m 的质数 p,然后计算 pm! 中出现的次数。这个次数就等于能整除 p 的数的数量加上能整除 p2 的数的数量加上能整除 p3 的数的数量,等等。这样一来,如果一个数含有 kp,其贡献就为 k,所以这个算法是正确的。故 pm! 中出现的的次数为:

pkmmpk

代码如下:

#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=1e5+5;
int pr[N],c[N],d[N],tot;int P,v[N];
inline void init(int m){
	const int n=N-5;
	for(int i=2;i<=n;i++){
		if(!v[i])pr[++tot]=v[i]=i;
		for(int j=1;j<=tot;j++){
			if(pr[j]>v[i]||pr[j]>n/i)break;
			v[pr[j]*i]=pr[j];
		}
	}
	for(int i=1;i<=tot;i++){
		if(m<i)break;
		ll x=pr[i];
		while(m>=x){
			c[i]+=m/x;
			x*=pr[i];
		}
	}
}
inline ll dow(ll a,int b){
	ll ret=1;
	while(b--){
		ret=(ret*a)%P;
		a--;
	}
	return ret;
}
int n,m,K;
int main(){
	scanf("%d%d%d%d",&n,&m,&K,&P);
	init(m);ll ans=1;
	for(int i=K;i<K+m;i++)d[i-K]=i;
	for(int i=1,p;i<=tot;i++){
		p=pr[i];if(!c[i])break;
		for(int j=(K%p?(K/p+1)*p:K);j<K+m;j+=p){
			while(c[i]&&d[j-K]%p==0)d[j-K]/=p,c[i]--;
		}
	}
	for(int i=K;i<K+m;i++)ans=(ans*d[i-K])%P;
	printf("%lld\n",dow(ans,n));
	return 0;
}

P2158 仪仗队#

一个人 (x,y) 能被看到,当且仅当 x+y=1gcd(x,y)=1(这里下标从 0 开始)。然后因为正方形是沿对称轴对称的,所以我们可以先算出答案的不严格一半,即 xy 的情况。

于是,问题转化为:求 ans=x=1Ny=1x[gcd(x,y)=1]=x=1nφ(x)

线性筛一下就好了,答案为 2(ans1)+3,其中 1 是减去 (1,1) 的情况(因为这个点在对称轴上)+3 是加上 (0,1),(1,0),(1,1) 的情况。

细节:注意 φ(1)=1,需要提前赋值。注意输入要减一,而且要特判输入等于一的情况。

该算法的时间复杂度为 O(n)

我的代码:

#include<cstdio>
#include<vector>
using namespace std;
const int N=4e4+5;
int v[N],phi[N];
long long ans;
vector<int>p;
inline void init(int n){
	ans=phi[1]=1;
	for(int i=2;i<=n;i++){
		if(!v[i]){v[i]=i;phi[i]=i-1;p.push_back(i);}
		for(int pr:p){
			if(pr>v[i]||pr>n/i)break;
			v[pr*i]=pr;
			phi[pr*i]=phi[i]*(pr==v[i]?pr:phi[pr]);
		}
		ans+=phi[i];
	}
}
int n;
int main(){
	scanf("%d",&n);
	init(n-1);
	printf("%lld",(n-1)?((ans-1)<<1)+3:0ll);
	return 0;
}

同余#

amodm=bmodm,则称 ab(modm)

同余类和剩余系#

定义#

对于 a[0,m1],将集合 {xNxa(modm)}={a+kmkN} 称为模 m 的一个同余类,简记为 a¯

m 个同余类构成 m 的完全剩余系(即 {0¯,1¯,,m1¯})。

1m 中与 m 互质的数所代表的剩余系有 φ(m) 个,它们构成 m 的简化剩余系。

性质#

简化剩余系关于模 m 乘法封闭。

证明#

a,bm 简化剩余系中的两个数,a=k1m+x,b=k2m+y (1x,y<m)

ab=(k1m+x)(k2m+y)=k1k2m2+k2mx+k1my+xy,所以 abxy(modm)

根据定义,gcd(x,m)=gcd(y,m)=1,所以 gcd(xy,m)=1

根据欧几里得算法公式,gcd(xy,m)=gcd(m,xymodm)=1,则简化剩余系关于模 m 乘法封闭。

证毕。

欧拉定理和费马小定理#

内容#

欧拉定理:若正整数 a,n 互质,则 aφ(n)1(modn)

费马小定理:若 p 是质数,则对于任意整数 a,有 apa(modn)

证明#

n 的简化剩余系为 {a1¯,a2¯,,aφ(n)¯}。根据定义,1ai<n

gcd(a,n)=1,gcd(ai,n)=1

gcd(aai,n)=1gcd(n,aaimodn)=1

aiaaimodn¯ 都在 n 的简化剩余系中。

aaimodn¯aajmodn¯ 是相同的同余类,则 aaiaaj(modn),即 a(aiaj)0(modn)

na(aiaj)n(aiaj)

aiaj(modn)

ai=aj

于是我们得到一个成立的命题:『如果 aaimodn¯aajmodn¯ 是相同的同余类,那么ai=aj』。因此它的逆否命题『如果 aiaj,那么 aaimodn¯aajmodn¯ 不是相同的同余类』也成立。

{aa1modn¯,aa2modn¯,,aaφ(n)modn¯}={a1¯,a2¯,,aφ(n)¯}

于是:

aφ(n)i=1φ(n)aii=1φ(n)aaii=1φ(n)ai(modn)

aφ(n)1(modn)

至于费马小定理,其实就是欧拉定理的一个特例(除了 pn 的情况,不过这种情况下费马小定理显然成立)。

欧拉定理的推论#

内容#

gcd(a,n)=1,ababmodφ(n)(modn)

证明#

abaφ(n)bφ(n)+bmodφ(n)1bφ(n)×abmodφ(n)abmodφ(n)(modn)

扩展欧拉定理#

bφ(n),ababmodφ(n)+φ(n)(modn)

证明

例题#

P5091 【模板】扩展欧拉定理#

套公式即可。代码是很久以前写的,所以码风可能和现在不一样。

#include<cctype>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
inline ll read(){
	char c=getchar();ll x=0;bool f=0;
	for(;!isdigit(c);c=getchar())f^=!(c^45);
	for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+(c^48);
	return f?-x:x;
}
inline ll getB(ll mod){
	char c=getchar();ll b=0;bool f=0;
	for(;!isdigit(c);c=getchar());
	for(;isdigit(c);c=getchar()){
		b=(b<<1)+(b<<3)+(c^48);
		if(b>mod)b%=mod,f=1;
	}
	return f?b+mod:b;
}
inline ll phi(ll x){
	ll res=x;
	for(register ll i=2;i*i<=x;i++){
		if(x%i==0){
			res=res*(i-1)/i;
			while(x%i==0)x/=i;
		}
	}
	if(x>1)res=res*(x-1)/x;
	return res;
}
inline ll quickPower(ll a,ll b,ll n){
	ll res=1;
	while(b){
		if(b&1){
			res=(res*a)%n;
		}
		a=(a*a)%n;
		b>>=1;
	}
	return res;
}
ll a,m,phim,b;
int main(){
	a=read();m=read();
	phim=phi(m);
	b=getB(phim);
	printf("%lld",quickPower(a,b,m));
	return 0;
}

数论分块#

过程#

对于形如 i=1nf(i)g(ni) 的式子,由于 ni 的取值至多只有 2n 种,因此可以把和式分块计算,而 f(i) 使用前缀和处理。

结论:值 ni 所在块的右端点为

nni

我们也可以类似地搞一个二维(或多维)数论分块,用于求解类似 i=1nf(i)g(ni)h(mi) 的式子。这时我们应该取右端点为所有维度右端点的最小值。例如二维数论分块应为 r=min(n/(n/i),m/(m/i))

显然,二维数论分块的时间复杂度仍然是 O(n) 的(假设 nm 同级)。

证明#

k=ni

kninknni=i=iinni

例题#

UVA11526#

题意

i=1nniT 组数据。

分析

模板题。按照上面的流程即可。注意边界情况和运算顺序。

时间复杂度为 O(Tn)

代码

#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
ll H(ll n){
	ll ret=0;
	for(ll l=1,r=0;l<=n;l=r+1){
		r=n/(n/l);ret+=(r-l+1)*(n/l);
	}
	return ret;
}
int main(){
	ll T,n;
	scanf("%lld",&T);
	while(T--){
		scanf("%lld",&n);
		printf("%lld\n",H(n));
	}
	return 0;
}

狄利克雷卷积#

从现在开始,数论函数一律使用粗黑体(如 f,g)或希腊字母(如 ε)。

定义两个数论函数的加法为 (f+g)(n)=f(n)+g(n),点乘为 (fg)(n)=f(n)g(n)。下面将省略点乘符号。

f 是完全积性函数时,(fg)(fh)=f(gh)

定义#

运算

(fg)(n)=dnf(d)g(nd)

为狄利克雷卷积。也可以写成:

(fg)(n)=ij=nf(i)g(j)

性质#

fg=gff(gh)=(fg)h(f+g)h=fh+ghxfg=x(fg)

有单位元 ε(n)=[n=1],即 fε=f

f,g 都是积性函数时,fg 也是积性函数。

一个积性函数的逆也是积性函数。

对于任意 f(1)0 的函数 f,存在逆元 g 使得 fg=ε

gf 的逆元,只需要满足以下式子即可:

g(n)=1f(1)([n=1]dn,d1f(d)g(nd))

证明#

挑一些不那么明显的。

结合律:

f(gh)(n)=ij=nf(i)kl=jg(k)h(l)=ikl=nf(i)g(k)h(l)

这就与三个函数的顺序无关了。

f 的逆元 g

fg=εdnf(d)g(nd)=[n=1]g(n)f(1)+dn,d1f(d)g(nd)=[n=1]g(n)=1f(1)([n=1]dn,d1f(d)g(nd))

其他先鸽了。

一些常见的数论函数及其性质#

定义#

1(x)=1id(x)=x,idk(x)=xkφ(x)=i=1x[xi]σ0(x)=d(x)=dx1σ(x)=dxd,σk(x)=dxdk

定义 μ1 的逆元。即 μ1=ε

性质#

定义中提到的所有函数均为积性函数。其中前两个是完全积性函数。

d=11σ=1idid=1φσ=11φ=dφφ=idμ1=dμid=σμ

其实只要记住前三个,后面的都可以自己推出来。

μ(pk)={1k=01k=10k>1

这个式子自己代入定义就很容易得到了。

φ(xy)=φ(x)φ(y)gcd(x,y)φ(gcd(x,y))d(xy)=ixjy[ij]

证明#

前两个都很显然。这里证明第三个。设 p 为任意质数。

(1φ)(pk)=dpkφ(d)=1+i=1kφ(pi)=1+i=1kpi(11p)=1+i=1kpipi1=pk

由于 1φ 是积性函数,所以有:

(1φ)(n)=(1φ)(pk)=pk=n=id(n)

倒数第二个可以用前面提到的欧拉函数的性质证明。以后做题推式子要注意不要忘记前面提到的公式。

最后一个可以通过分类讨论质数在 x,y 中的分布情况证明。

莫比乌斯反演#

定理#

g=f1,则有 f=gμ

当然,一般的题目肯定不会直接写出卷积的形式。

所以我们这么写:

g(n)=dnf(d)f(n)=dnμ(d)g(nd)

魔力筛#

虽然在网上没看见过这种叫法,倒是搜到了魔力筛让天价药物降价,但机房都这么叫,我也暂且这么叫吧。

这是一个可以在 O(nloglogn) 的时间复杂度求出 任意数论函数μ 的狄利克雷卷积的算法。当然,必须保证这个数论函数能被提前计算出来。

gi,n=dnf(d)μ(nd),其中 d 只含前 i 种质因子。则有:

gi,n={gi1,npingi1,ngi1,npipin

需要滚动数组优化。

虽然可以被解释为高维差分,但我不会。

其实这个 DP 方程也比较好理解。第一种情况显然正确。我们来看看第二种:

dnf(d)μ(nd)=xnf(x)μ(nx)ynpif(y)μ(npiy)

其中 d 只包含前 i 种质因子,x,y 只包含前 i1 种质因子。我们知道,每多一个质因子,μ 的取值就会乘以 1,因此上式的意义是:强制不选 pi,然后乘以 1 累加在答案上。如果含有平方因子,则值为 0,不影响答案。

复杂度和埃氏筛一样。

参考代码:

inline void calc(int n){
	for(int i=1;i<=n;i++)g[i]=f[i];
	for(int p:pr){
		for(int i=n/p;i>=1;i--){
			g[i*p]=g[i*p]-g[i];
		}
	}
}

应用#

练习题#

题目描述

给定 n,m106,求

i=1nj=1mgcd(i,j)

题目分析

我们知道 dgcd(a,b)dadb

有一个常见的套路:若要求 f(gcd(x,y)),可以先找到一个数论函数 g=1f,这样子就有:

f(gcd(x,y))=dgcd(x,y)g(d)=dxdyg(d)=g(d)[dx][dy]

于是解决这道题就变得很简单了。我们知道 id=1φ。所以好像并不需要莫比乌斯反演

i=1nj=1mgcd(i,j)=i=1nj=1mid(gcd(i,j))=i=1nj=1mdidjφ(d)=d=1min{n,m}φ(d)i=1nj=1m[di][dj]=d=1min{n,m}φ(d)ndmd

然后就可以用二维数论分块搞了。不过现在应该不会出这么套路的题了吧。

[国家集训队]Crash的数字表格 / JZPTAB#

题目描述

给定 n,m,求

i=1nj=1mlcm(i,j)

20101009 取模的结果。

数据范围:1n,m107

题目分析

id1=g1,则 g=id1μ

i=1nj=1mlcm(i,j)=i=1nj=1mijid1(gcd(i,j))=i=1nj=1mijdidjg(d)=d=1min{n,m}g(d)i=1nj=1m[di][dj]ij=14d=1min{n,m}g(d)d2nd(nd+1)md(md+1)

g 显然是积性函数,并且在质数幂处很容易求。因此我们可以线性计算之。

时间复杂度为 O(n)数论分块好像没有必要,如果不用数论分块甚至可能会快一点

代码

#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=1e7+5,P=20101009;
int v[N],t[N];
vector<int>pr;
ll inv[N],g[N],s[N];
inline void init(int n){
	inv[1]=1;g[1]=1;s[1]=1;
	for(int i=2;i<=n;i++){
		inv[i]=(P-P/i)*inv[P%i]%P;
		if(!v[i]){
			g[i]=(inv[i]+P-1)%P;
			pr.push_back(t[i]=v[i]=i);
		}else{
			if(i==t[i])g[i]=(inv[i]-inv[i/v[i]]+P)%P;
			else g[i]=g[t[i]]*g[i/t[i]]%P;
		}
		for(int p:pr){
			if(p>v[i]||p>n/i)break;
			v[i*p]=p;t[i*p]=(v[i]==p?t[i]*p:p);
		}
		s[i]=(s[i-1]+g[i]*i%P*i%P)%P;
	}
}
inline ll calc(int n,int m){
	ll ret=0;
	for(int l=1,r;l<=n;l=r+1){
		r=min({n,n/(n/l),m/(m/l)});
		ret=(ret+(s[r]-s[l-1]+P)%P*(n/l)%P*(n/l+1)%P*(m/l)%P*(m/l+1)%P)%P;
	}
	return ret*inv[4]%P;
}
int main(){
	int n,m;scanf("%d%d",&n,&m);
	if(n>m)swap(n,m);init(n);
	printf("%lld\n",calc(n,m));
	return 0;
}

[SDOI2017]数字表格#

题目描述

给定 n,m,求

i=1nj=1mFgcd(i,j)

109+7 取模的结果。其中 Fi 表示斐波那契数列的第 i 项。

T 组数据。数据范围:1T1031n,m106

题目分析

我们定义 f(n)=lnFng1=f。则:

i=1nj=1mFgcd(i,j)=expi=1nj=1mf(gcd(i,j))=expi=1nj=1mdidjg(gcd(i,j))=expd=1min{n,m}g(d)i=1nj=1m[di][dj]=expd=1min{n,m}g(d)ndmd=d=1min{n,m}exp(ij=df(i)μ(j)ndmd)=d=1min{n,m}(idexp(f(i)μ(di)))ndmd=d=1min{n,m}(idFiμ(di))ndmd

这个式子可以外层数论分块,内层暴力枚举计算。

单次询问的时间复杂度为 O(n)。注意对 μ 的处理。

另一种方法是使用类似魔力筛的方法计算内层,从而使预处理的时间复杂度降到 O(nloglogn),请自行参阅题解。

代码

#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=1e6+5,P=1e9+7;
inline ll _pow(ll a,ll b){
	ll ret=1;a%=P;
	while(b){if(b&1)ret=(ret*a)%P;a=a*a%P;b>>=1;}
	return ret;
}
int mu[N],v[N];
ll F[N],G[N];
vector<int>pr;
inline void init(){
	mu[1]=1;F[0]=0;F[1]=1;G[0]=G[1]=1;
	const int n=N-5;
	for(int i=2;i<=n;i++){
		G[i]=1;F[i]=(F[i-1]+F[i-2])%P;
		if(!v[i])pr.push_back(v[i]=i),mu[i]=-1;
		for(int p:pr){
			if(p>v[i]||p>n/i)break;
			v[i*p]=p;
			if(v[i]==p)mu[i*p]=0;
			else mu[i*p]=mu[i]*mu[p];
		}
	}
	ll mF;
	for(int i=2;i<=n;i++){
		mF=_pow(F[i],P-2);
		for(int j=i;j<=n;j+=i){
			if(mu[j/i]==-1)G[j]=G[j]*mF%P;
            else if(mu[j/i]==1)G[j]=G[j]*F[i]%P;
		}
	}
	for(int i=2;i<=n;i++){
        G[i]=G[i-1]*G[i]%P;
    }
}
int main(){
	int T,n,m;scanf("%d",&T);
	ll ans;init();
	while(T--){
		scanf("%d%d",&n,&m);
		if(n>m)swap(n,m);
		ans=1;
		for(int l=1,r;l<=n;l=r+1){
			r=min(n/(n/l),m/(m/l));
			ans=ans*_pow(G[r]*_pow(G[l-1],P-2)%P,(ll)(n/l)*(m/l))%P;
		}
		printf("%lld\n",ans);
	}
	return 0;
}

YY 的 GCD#

题目大意

P 为所有质数构成的集合。求

i=1nj=1m[gcd(i,j)P]

T 组数据。数据范围:T=104n,m107。TL:4s。

题目分析

f(n)=[nP]g1=f

然后就和上面一样了...... 直接写结果吧。

d=1min{n,m}g(d)ndmd

由于 f 不是积性函数,我们需要用魔力筛计算 g。然后就可以数论分块搞了。

时间复杂度为 O(nloglogn+Tn)

代码

#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=1e7+5;
int v[N],g[N];
vector<int>pr;
inline void init(){
	const int n=N-5;
	for(int i=2;i<=n;i++){
		if(!v[i])pr.push_back(v[i]=i),g[i]=1;
		for(int p:pr){
			if(p>v[i]||p>n/i)break;
			v[p*i]=p;
		}
	}
	for(int p:pr){
		for(int i=n/p;i>=1;i--)g[i*p]-=g[i];
	}
	for(int i=1;i<=n;i++)g[i]+=g[i-1];
}
int main(){
	init();
	int n,m,T;scanf("%d",&T);
	ll ans;
	while(T--){
		scanf("%d%d",&n,&m);
		if(n>m)swap(n,m);ans=0;
		for(int l=1,r;l<=n;l=r+1){
			r=min({n,n/(n/l),m/(m/l)});
			ans+=(ll)(g[r]-g[l-1])*(n/l)*(m/l);
		}
		printf("%lld\n",ans);
	}
	return 0;
}
posted @   hihihi198  阅读(138)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示
主题色彩