数论习题(一)

(一).推式子题

- P2568 GCD

给定 n,求:

pPi=1nj=1n[gcd(i,j)=p]

其中 P 为质数集。

推式子:

pPi=1nj=1n[gcd(i,j)=p]=pPi=1npj=1np[gcd(i,j)=1]=pP(i=1np(2j=1i[gcd(i,j)=1])1)=pP(i=1np(2φ(i))1)

:感性理解,np 以内的互质数对对数,等于二倍的 ij 的对数,减掉重复计算的 (1,1) 数对。

线性筛 φ 并求前缀和,枚举 p 统计即可。

提交记录

点击查看代码
//#pragma GCC optimize(2)
//#pragma GCC optimize(3)
//#pragma GCC optimize("Ofast","inline")
#include<bits/stdc++.h>
#define fr(a) freopen(a,"r",stdin)
#define fw(a) freopen(a,"w",stdout)
using namespace std;
typedef long long ll;
const int MAXN=1e7+10;
ll n,prime[MAXN],phi[MAXN],v[MAXN],tot,sumphi[MAXN];
ll ans;
void shai(){//欧拉筛求质数&欧拉函数
	phi[1]=1;
	for(int i=2;i<=n;i++){
		if(v[i]==0){
			v[i]=i;prime[++tot]=i;
			phi[i]=i-1;
		}
		for(int j=1;j<=tot;j++){
			if(prime[j]>v[i] || prime[j] > n/i) break;
			v[i*prime[j]]=prime[j];
			if(i%prime[j]==0) phi[i*prime[j]] =phi[i] * prime[j];
			else phi[i*prime[j]] =phi[i] *(prime[j]-1);
		}
	}
	for(int i=1;i<=n;i++) phi[i]+=phi[i-1];
}
int main(){
	scanf("%lld",&n);
	shai();
	for(int i=1;i<=tot;i++) ans+=2*phi[n/prime[i]]-1;
	printf("%lld\n",ans);
	return 0;
}

- P2398 GCD SUM

给定 n,求:

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

枚举 gcd,枚举推式子:

i=1nj=1ngcd(i,j)=d=1n(di=1ndj=1nd[gcd(i,j)=1])=d=1n(d(i=1nd(2φ(i))1))和上一题一样的操作

φ,前缀和,枚举 d,搞定!

提交记录

点击查看代码
//#pragma GCC optimize(2)
//#pragma GCC optimize(3)
//#pragma GCC optimize("Ofast","inline")
#include<bits/stdc++.h>
#define fr(a) freopen(a,"r",stdin)
#define fw(a) freopen(a,"w",stdout)
#define MP(a,b) make_pair(a,b)
#define DEBUG
using namespace std;
typedef long long ll;
const int N=1e5+10;
ll n;
ll prime[N],v[N],phi[N],tot;
void shai(){//欧拉筛求质数&欧拉函数
	phi[1]=1;
	for(int i=2;i<=n;i++){
		if(v[i]==0){
			v[i]=i;prime[++tot]=i;
			phi[i]=i-1;
		}
		for(int j=1;j<=tot;j++){
			if(prime[j]>v[i] || prime[j] > n/i) break;
			v[i*prime[j]]=prime[j];
			if(i%prime[j]==0) phi[i*prime[j]] =phi[i] * prime[j];
			else phi[i*prime[j]] =phi[i] *(prime[j]-1);
		}
	}
	for(int i=1;i<=n;i++) phi[i]+=phi[i-1];
}
int main(){
	scanf("%lld",&n);ll ans=0;
	shai();
	for(int d=1;d<=n;d++){
		ans+=d*(2*phi[n/d] -1);
	}printf("%lld\n",ans);
	return 0;
}


- P2522 [HAOI2011] Problem b

给定 1(ab,cd,k)5104,求:

i=abj=cd[gcd(i,j)=k]

先通过容斥拆成四部分:记 A=ak,B=bk,C=ck,D=dk

i=abj=cd[gcd(i,j)=k]=i=ABj=CD[gcd(i,j)=1]=i=1Bj=1D[gcd(i,j)=1]i=1Bj=1C1[gcd(i,j)=1]i=1A1j=1D[gcd(i,j)=1]+i=1A1j=1C1[gcd(i,j)=1]

f(n,m)=i=1nj=1m[gcd(i,j)=1],则

原式=f(B,D)f(B,C1)f(A1,D)+f(A1,C1)

开始推 f

f(n,m)=i=1nj=1m[gcd(i,j)=1]=i=1nj=1mdgcd(i,j)μ(d)好像是莫反的结论?=d=1min(n,m)(μ(d)i=1n[di]j=1m[dj])更换求和顺序,感性理解=d=1min(n,m)(μ(d)ndmd)n 以内 d 的倍数一共有 nd 个

最后这个式子可以数论分块求,这样这道题就做完了。

提交记录

点击查看代码
//#pragma GCC optimize(2)
//#pragma GCC optimize(3)
//#pragma GCC optimize("Ofast","inline")
#include<bits/stdc++.h>
#define fr(a) freopen(a,"r",stdin)
#define fw(a) freopen(a,"w",stdout)
#define MP(a,b) make_pair(a,b)
#define DEBUG
using namespace std;
typedef long long ll;
const int N=5e4+10;
ll T=1;
ll a,b,c,d,k;
ll prime[N],v[N],mu[N],tot,summu[N];
void shai(){
	mu[1]=1;
	for(int i=2;i<=N-10;i++){
		if(v[i]==0){
			v[i]=i;prime[++tot]=i;
			mu[i]=-1;
		}
		for(int j=1;j<=tot;j++){
			if(prime[j]>v[i] || prime[j] > (N-10)/i) break;
			v[i*prime[j]]=prime[j];
			if(i % prime[j]==0) mu[i*prime[j]]=0;
			else mu[i*prime[j]]=-mu[i];
		}
	}
	for(int i=1;i<=N-10;i++) summu[i]=summu[i-1]+mu[i];
}
ll calc(ll n,ll m){
	ll res=0,l=1,lim=min(n,m);bool Out=0;
	while(l<=lim){
		ll pn=n/l,pm=m/l;
		ll r=min(n/pn,m/pm);
		if(r>=lim) r=lim,Out=1;
		res+=(summu[r]-summu[l-1]) * pn * pm;
		if(Out) break;
		l=r+1;
	}
	return res;
}
ll calc2(ll n,ll m){
	ll res=0;
	for(int d=1;d<=min(n,m);d++){
		res+=mu[d] * (n/d) * (m/d);
	}return res;
}
int main(){
	shai();
	scanf("%lld",&T);
	for(int Case=1;Case<=T;Case++){
		scanf("%lld%lld%lld%lld%lld",&a,&b,&c,&d,&k);
		ll ans=0;
		ll A=ceil((double)a/k)-1,B=b/k,C=ceil((double)c/k)-1,D=d/k;
		ans=calc(B,D)-calc(B,C)-calc(A,D)+calc(A,C);
		printf("%lld\n",ans);
	}
	return 0;
}

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

给定 n,m,求:

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

由小学奥数可知:lcm(i,j)=ijgcd(i,j),所以:

i=1nj=1mlcm(i,j)=i=1nj=1mijgcd(i,j)=D=1min(n,m)(1Di=1nDj=1mD([gcd(i,j)=1]ijD2))=D=1min(n,m)(Di=1nDj=1mD([gcd(i,j)=1]ij))

接下来我们化简括号内,D 之后的一部分(记为 f(n,m)):

f(n,m)=i=1nj=1m([gcd(i,j)=1]ij)=i=1nj=1m((dgcd(i,j)μ(d))ij)=d=1min(n,m)(μ(d)i=1nj=1m([di][dj]ij))

此式中 μ(d) 之后的一部分,相当于这个东西:

(d+2d++ndd)(d+2d++mdd)=d2(1+2++nd)(1+2++md)

所以:

f(n,m)=d=1min(n,m)(μ(d)d2i=1ndj=1md(ij))

而中间那个求和符号:

i=1ndj=1md(ij)=(i=1ndi)(i=1mdj)=md(md+1)2nd(nd+1)2

所以:

f(n,m)=d=1min(n,m)(μ(d)d2md(md+1)2nd(nd+1)2)

f 就可以用数论分块来算。

而原式显然也可以数论分块来做,两个合二为一就能过掉此题了。

最后放一下原式最终的样子:

D=1min(n,m)(Di=1nDj=1mD([gcd(i,j)=1]ij))=D=1min(n,m)(Df(nD,mD))=D=1min(n,m)(Dd=1min(nD,mD)(μ(d)d2nDd(nDd+1)2mDd(mDd+1)2))

提交记录

点击查看代码
//#pragma GCC optimize(2)
//#pragma GCC optimize(3)
//#pragma GCC optimize("Ofast","inline")
#include<bits/stdc++.h>
#define fr(a) freopen(a,"r",stdin)
#define fw(a) freopen(a,"w",stdout)
#define MP(a,b) make_pair(a,b)
#define DEBUG
using namespace std;
typedef long long ll;
const int N=1e7+10,mod=20101009,inv2=10050505,inv4=15075757;
ll n,m;
ll prime[N],v[N],mu[N],tot;
ll sum2[N];
void shai(ll n){
	mu[1]=1;
	for(ll i=2;i<=n;i++){
		if(v[i]==0){
			v[i]=i;prime[++tot]=i;
			mu[i]=-1;
		}
		for(ll j=1;j<=tot;j++){
			if(prime[j]>v[i] || prime[j] > n/i) break;
			v[i*prime[j]]=prime[j];
			if(i % prime[j]==0) mu[i*prime[j]]=0;
			else mu[i*prime[j]]=-mu[i];
		}
	}
}
ll calc(ll n,ll m){
	ll res=0;
	ll l=1,lim=min(n,m);bool Out=0;
	while(l<=lim){
		ll nl=n/l,ml=m/l,r=min(n/nl,m/ml);
		if(r>=lim) r=lim,Out=1;
		ll cnt=nl*(nl+1)%mod*ml%mod*(ml+1)%mod *inv4%mod;
		res=(res+(sum2[r]-sum2[l-1])*cnt%mod)%mod;
		if(Out) break;
		l=r+1;
	}
	return (res%mod+mod)%mod;
}
int main(){
	scanf("%lld%lld",&n,&m);
	shai(max(n,m));
	for(ll i=1;i<=max(n,m);i++) sum2[i]=(sum2[i-1]+i*i%mod*mu[i]%mod)%mod;
	ll ans=0,l=1,lim=min(n,m);
	bool Out=0;
	while(l<=lim){
		ll r=min(n/(n/l),m/(m/l));
		if(r>=lim) r=lim,Out=1;
		ans=(ans+calc(n/l,m/l)*(r*(r+1)%mod-l*(l-1)%mod)%mod*inv2%mod)%mod;
		if(Out) break;
		l=r+1;
	}
	printf("%lld\n",(ans%mod+mod)%mod);
	return 0;
}


- BZOJ2694. Lcm

给定 1t1041n,m4106,求 i[1,n],j[1,m]n,n2gcd(i,j) 的数对 (i,j) 个数,即:

i=1nj=1mμ2(gcd(i,j))lcm(i,j)

直接推式子,把 lcm 变成 gcd,然后枚举 gcd

d=1min(n,m)μ2(d)i=1nj=1m[gcd(i,j)=d]ijd

lcm 的处理和上一题很像:

d=1min(n,m)μ2(d)di=1ndj=1md[gcd(i,j)=1]ij

这样子上一题中的 f 就出来了,省略中间一大堆推导过程:

d=1min(n,m)(μ2(d)dk=1min(nd,md)(μ(k)k2ndk(ndk+1)2mdk(mdk+1)2))

发现 kdmin(n,m),令 T=kd,经典套路,枚举 T注:在做这道题前我全然不知道这种套路

T=1min(n,m)nT(nT+1)2mT(mT+1)2kTμ(k)k2Tkμ2(Tk)=T=1min(n,m)nT(nT+1)2mT(mT+1)2TkTkμ(k)μ2(Tk)

f(k)=kμ(k),g(k)=μ2(k),h(T)=kTkμ(k)μ2(Tk)

f,g 为积性函数,又 h=fg,得 h 为积性函数。

考虑用线性筛筛出来 h,易得:

h(pk){1k=01pk=1pk=20k>2

这样就可以用线性筛筛出h 了。

最后对原式进行数论分块,卡卡常就过去了。

提交记录

点击查看代码
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast","inline")
#include<bits/stdc++.h>
#define fr(a) freopen(a,"r",stdin)
#define fw(a) freopen(a,"w",stdout)
#define MP(a,b) make_pair(a,b)
#define DEBUG
using namespace std;
typedef int ll;
const int N=4e6+10,mod=1<<30;
ll T=1;
ll n,m,sum[N];
ll prime[N],v[N],g[N],h[N],tot;
void shai(ll n){
	h[1]=1;
	for(int i=2;i<=n;i++){
		if(v[i]==0){
			prime[++tot]=i;
			v[i]=i;g[i]=i;
		}
		for(int j=1;j<=tot;j++){
			if(prime[j]>v[i]||prime[j]>n/i) break;
			v[i*prime[j]]=prime[j];
			if(i%prime[j]==0) g[i*prime[j]]=g[i]*prime[j];
			else g[i*prime[j]]=prime[j];
		}
	}
	for(int i=2;i<=n;i++){
		if(i==g[i]){
			if(g[i]==v[i]) h[i]=1-v[i];
			else if(g[i]==v[i]*v[i]) h[i]=-v[i];
			else h[i]=0;
		}else h[i]=(h[i/g[i]]*h[g[i]])%mod;
	}
	for(int i=1;i<=n;i++) h[i]=(h[i]*i%mod+h[i-1])%mod,sum[i]=(sum[i-1]+i)%mod;
}
int main(){
	shai(N-10);
	scanf("%d",&T);
	for(int Case=1;Case<=T;Case++){
		scanf("%d%d",&n,&m);
		ll ans=0;
		for(int l=1,r;l<=min(n,m);l=r+1){
            ll nl=n/l,ml=m/l;
			r=min(n/nl,m/ml);
			ans=(ans+sum[nl]*sum[ml] %mod *(h[r]-h[l-1])%mod)%mod;
        }
		printf("%d\n",(ans+mod)%mod);
	}
	return 0;
}

- P4449 于神之怒加强版

给定 1T21031n,m5106,和一个固定的 1α5106,求:

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

套路!!枚举 gcd

d=1min(n,m)dαi=1ndj=1md[gcd(i,j)=1]

莫反拆开:

d=1min(n,m)dαi=1ndj=1mdkgcd(i,j)μ(k)

枚举 k

d=1min(n,m)dαk=1min(nd,md)μ(k)i=1ndkj=1mdk1

也就是:

d=1min(n,m)dαk=1min(nd,md)μ(k)ndkmdk

枚举 T=dk

T=1min(n,m)nTmTdTdαμ(Td)

后面那个东西其实是 idαμ,显然是积性函数,可以线性筛筛出来pP,k1):

(idαμ)(pk)=pαkpα(k1)

筛出来后求个前缀和,然后数论分块即可。

提交记录

点击查看代码
//#pragma GCC optimize(2)
//#pragma GCC optimize(3)
//#pragma GCC optimize("Ofast","inline")
#include<bits/stdc++.h>
#define fr(a) freopen(a,"r",stdin)
#define fw(a) freopen(a,"w",stdout)
#define MP(a,b) make_pair(a,b)
#define DEBUG
using namespace std;
typedef long long ll;
const int N=5e6+10,mod=1e9+7;
ll T=1,k;
ll n,m;
ll prime[N],tot,v[N],mu[N];
ll g[N],f[N];
ll qpow(ll a,ll n){
	ll res=1;
	while(n){
		if(n&1) res=(res*a) %mod;
		a=(a*a)%mod,n>>=1;
	}return res;
}
void shai(ll n){
	f[1]=mu[1]=g[1]=f[1]=1;
	for(int i=2;i<=n;i++){
		if(!v[i]){
			v[i]=i,prime[++tot]=i,mu[i]=-1;
			g[i]=i;
		}
		for(int j=1;j<=tot;j++){
			if(prime[j]>v[i]||prime[j]>n/i) break;
			v[i*prime[j]]=prime[j];
			if(i%prime[j]==0) g[i*prime[j]]=g[i]*prime[j],mu[i*prime[j]]=0;
			else g[i*prime[j]]=prime[j],mu[i*prime[j]]=-mu[i];
		}
	}
	for(int i=2;i<=n;i++){
		if(i==g[i]){
			ll tot=0;
			while(g[i]&&g[i]%v[i]==0) tot++,g[i]/=v[i];
			f[i]=qpow(v[i],k*tot)-qpow(v[i],k*(tot-1));
			f[i]=(f[i]%mod+mod)%mod;
		}else f[i]=(f[i/g[i]]*f[g[i]])%mod;
	}
	for(int i=1;i<=n;i++) f[i]+=f[i-1];
}
int main(){
	scanf("%lld%lld",&T,&k);
	shai(N-10);
	for(int Case=1;Case<=T;Case++){
		scanf("%lld%lld",&n,&m);
		ll ans=0;
		for(int l=1,r;l<=min(n,m);l=r+1){
			ll nl=n/l,ml=m/l;r=min(n/nl,m/ml);
			(ans+=nl*ml%mod*(f[r]-f[l-1])%mod)%=mod;
		}printf("%lld\n",(ans+mod)%mod);
	}
	return 0;
}


- P3327 [SDOI2015] 约数个数和

给定 1t51041n,m5104,求:

i=1nj=1md(ij)

其中 d(n)n 的约数个数,即 dn1

蒙结论,蒙出来要求的就是这个东西:

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

原理大概就是:枚举一对互质(为什么不会遗漏我也不知道)的数对 (i,j),满足 ab 存在因数 ij,且 an,bm 的数对 (a,b) 的数量显然为 nimj

但是为什么枚举互质(i,j) 就能做到不重不漏,我也不得而知。

接下来这个东西就能做了,用莫反变一下 [gcd(i,j)=1]

i=1nj=1mdgcd(i,j)μ(d)nimj

套路性地,把 d 提出去:

d=1min(n,m)μ(d)(i=1n[di]ni)(i=1m[di]mi)

也就是:

d=1min(n,m)μ(d)(i=1ndndi)(i=1mdmdi)

我们 O(nn) 预处理一下 i=1nni ,原式最外层整除分块,这个就做完了。

提交记录


附:关于此题正常一些的想法

正常情况下应该蒙出这个结论的:

d(nm)=injm[gcd(i,j)=1]

我们先把这个证了。

记:

nm=i=1kpiai+bi,n=i=1kpiai,m=i=1kpibi

我们知道 d(nm)=i=1k(ai+bi+1),我们分别考虑每个质因子。

对于某个质因子 pi,我们分别考虑等式左右的取法。

对于等式左边,显然取法有 ai+bi+1 种。

对于等式右边,有三类取法:

{ain 中 p 取 1ai 个,m 中 p 取 0 个bin 中 p 取 0 个,m 中 p 取 1bi 个1n 中 p 取 0 个,m 中 p 取 0 个

加起来一共有 ai+bi+1 种取法。

而每个质因子都是一样的情形,所以总数相同。


有了这个结论,我们正常地推一遍式子:

i=1nj=1mxiyj[gcd(x,y)=1]

直接考虑更换 x,yi,j 的枚举顺序:

x=1ny=1mi=1nj=1m[xi][yj][gcd(x,y)=1]

i=1n[xi]=nx,所以原式:

x=1ny=1mnxmy[gcd(x,y)=1]

此时已经和我蒙出来的结论一模一样了,这就OK了。

点击查看代码
//#pragma GCC optimize(2)
//#pragma GCC optimize(3)
//#pragma GCC optimize("Ofast","inline")
#include<bits/stdc++.h>
#define fr(a) freopen(a,"r",stdin)
#define fw(a) freopen(a,"w",stdout)
#define MP(a,b) make_pair(a,b)
#define DEBUG
using namespace std;
typedef long long ll;
const int N=5e4+10;
ll TT=1;
ll n,m;
ll prime[N],v[N],mu[N],tot;
ll ji[N];
ll calc(ll n){
	if(ji[n]) return ji[n];
	ll res=0;
	for(int l=1,r;l<=n;l=r+1){
		ll nl=n/l;
		r=n/nl;
		res+=nl * (r-l+1);
	}return ji[n]=res;
}
void shai(ll n){
	mu[1]=1;
	for(ll i=2;i<=n;i++){
		if(v[i]==0){
			v[i]=i;prime[++tot]=i;
			mu[i]=-1;
		}
		for(ll j=1;j<=tot;j++){
			if(prime[j]>v[i] || prime[j] > n/i) break;
			v[i*prime[j]]=prime[j];
			if(i % prime[j]==0) mu[i*prime[j]]=0;
			else mu[i*prime[j]]=-mu[i];
		}
	}
	for(int i=1;i<=n;i++) mu[i]+=mu[i-1],ji[i]=calc(i);
}
int main(){ 
	shai(N-10);
	scanf("%lld",&TT);
	for(int Case=1;Case<=TT;Case++){
		scanf("%lld%lld",&n,&m);
		ll ans=0;
		for(int l=1,r;l<=min(n,m);l=r+1){
			ll nl=n/l,ml=m/l;
			r=min(n/nl,m/ml);
			ans+=(mu[r]-mu[l-1]) * ji[nl]*ji[ml];
		}
		printf("%lld\n",ans);
	}
	return 0;
}


- BZOJ3561. DZY Loves Math VI

给定 1n,m5105,求:

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

直接推式子,枚举 gcd

d=1min(n,m)i=1ndi=1md[gcd(i,j)=1](ijd)d

dd 提出去,莫反拆开:

d=1min(n,m)ddi=1ndi=1mdkgcd(i,j)μ(k)(ij)d

套路地,枚举 k

d=1min(n,m)ddk=1min(nd,md)μ(k)i=1ndki=1mdk(ikjk)d

k2d 提出去,关于 i,j 的求和分开:

d=1min(n,m)ddk=1min(nd,md)μ(k)k2d(i=1ndkid)(i=1mdkjd)

本来我觉得的这个东西还能再变形,再做,于是想破脑袋想了一个上午没想出来。

但其实,我们分析分析复杂度:

d 的循环 O(n)

dk 的循环是 n1+n2+nn=O(nlogn)

如果再算上 ij 的循环,那就是 n1log(n1)+n2log(n2)+nnlog(nn),这个东西不超过 O(nlog2n),具体是多少不会算,但其实很小。

因为当 d 取了 d0 时,最内层用到的最大的幂就是 nd0d,于是就可以实时计算 1nd0d 次幂,如果在 d01 时算过了,只需要自乘一下,否则直接用快速幂算。因为每个自然数最多算一次快速幂,这一些预处理的总复杂度就是 O(nlogn) 的。

预处理之后,可以同时把幂的前缀和算出来,从时间复杂度就到了 O(nlogn)

所以直接枚举算这个式子就好了……我为什么浪费了一个上午啊啊啊啊啊啊啊啊啊啊啊啊

提交记录

点击查看代码
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast","inline")
#include<bits/stdc++.h>
#define fr(a) freopen(a,"r",stdin)
#define fw(a) freopen(a,"w",stdout)
#define MP(a,b) make_pair(a,b)
#define DEBUG
using namespace std;
typedef long long ll;
const int N=5e5+10,mod=1000000007;
int n,m;
int prime[N],v[N],mu[N],tot;
ll now[N],sum[N];
int tmp[N];
ll qpow(ll a,int n){
	int res=1,stn=n,sta=a;
	while(n){
		if(n&1) res=(1ll*res*a)%mod;
		a=(a*a)%mod,n>>=1;
	}
	return res;
}
void shai(int n){
	mu[1]=1,v[1]=1;
	for(int i=2;i<=n;i++){
		if(v[i]==0){
			v[i]=i;prime[++tot]=i;
			mu[i]=-1;
		}
		for(int j=1;j<=tot;j++){
			if(prime[j]>v[i] || prime[j] > n/i) break;
			v[i*prime[j]]=prime[j];
			if(i % prime[j]==0) mu[i*prime[j]]=0;
			else mu[i*prime[j]]=-mu[i];
		}
	}
}
int main(){
	shai(N-10);
	scanf("%d%d",&n,&m);
	int ans=0;
	for(int d=1;d<=min(n,m);d++){
		int tot=0,lim=min(n/d,m/d);
		for(int i=1;i<=max(n/d,m/d);i++){
			now[i]=((now[i]!=0)?now[i] *i%mod:qpow(i,d))%mod;
			sum[i]=(sum[i-1]+now[i])%mod;
		}
		for(int k=1;k<=lim;k++){
			int cnt1=0,cnt2=0;tmp[1]=1;
			int lim1=n/(d*k),lim2=m/(d*k);
			tot=(tot+ mu[k] * qpow(k,2*d)%mod *sum[lim1]%mod *sum[lim2]%mod )%mod;
		}
		ans=(ans+tot*qpow(d,d)%mod)%mod;
	}printf("%d\n",(ans+mod)%mod);
	return 0;
}

- P5518 [MtOI2019] 幽灵乐团 / 莫比乌斯反演基础练习题

推式子大成!

求:

i=1Aj=1Bk=1C(lcm(i,j)gcd(i,k))f(type)

其中:

f(0)=1f(1)=ijkf(2)=gcd(i,j,k)

预处理

我们将分子分母分离:

i=1Aj=1Bk=1C(lcm(i,j))f(type)i=1Aj=1Bk=1C(gcd(i,k))f(type)

也就是:

i=1Aj=1Bk=1C(ijgcd(i,j))f(type)i=1Aj=1Bk=1C(gcd(i,k))f(type)

对分子进行子母分离,分母扔下面:

i=1Aj=1Bk=1C(ij)f(type)(i=1Aj=1Bk=1Cgcd(i,j)f(type))(i=1Aj=1Bk=1Cgcd(i,k)f(type))

关键东西

我们定义一个神奇函数 g,满足:

g(x)={px=pα,α>01othercases

这个神奇函数可以 O(n) 求。

有了神奇函数,我们就可以推导一下这个式子(待会要用到两次,所以扔前头了):

i=1aj=1bgcd(i,j)

枚举 gcd,有:

d=1min(a,b)di=1adj=1bd[gcd(i,j)=1]

莫反拆开往外提:

d=1min(a,b)dk=1min(ad,bd)μ(k)adkbdk

把上面的求和捋下来,有:

d=1min(a,b)k=1min(ad,bd)dμ(k)adkbdk

枚举 T=dk,里面我们不枚举 d 枚举 k,有:

T=1min(a,b)kT(Tk)μ(k)adkbdk

我们把底数上的分子 T 和分母 k 拆开处理:

T=1min(a,b)(kTTμ(k)kTkμ(k))adkbdk

分子

因为底数为 T 不变,我们把求积符号写在指数上,有:

TkTμ(k)

这东西熟悉的不得了,不就是 1μ=ε 嘛!于是就成了:

T[T=1]

这玩意一眼顶针,铁定为 1

分母

我们记 f(T)=kTkμ(k)

没办法拆开了,于是我们考虑一下这玩意的实际含义。

我们对 T 进行质因数分解,记为 T=i=1mpici

我们也对 k 进行分解:k=i=1mpiti

注意到当存在 ti>1 时,μ(k)=0,对答案无贡献,于是我们只需考虑指数为 01 的情况;同时我们还可以得到一个推论:f(i=1mpici)=f(i=1mpi),其中 ci1

我们对 m 进行分讨:

m=0

此时 T=1,有 f(T)=1

m=1

此时 f(pα)=f(p)=pμ(p)=p1

m>1

我们把 f(T) 想象成一个分数。

我们我们发现,当指数 μ1 时,这个因数 k 出现在分子上;否则出现在分母上。

于是我们考虑某一个质因数 p 分别出现在分子和分母上的数量。

既然 p 要出现,那么 p 必须得选,在剩下的 m1 个因子中选择一些。

所以分子上的个数就是 0+Cm11+Cm13+

同理,分母上出现的个数是 Cm10+Cm12+

根据一点点组合数学的知识,我们会发现无论 m 的奇偶性,这俩玩意肯定相等!

既然对于每个质因子,上下的出现次数都相等,那么这一坨玩意就是 1

f(T)=1

综上所述,我们发现分子绝对是 1,而分母是这么个玩意,两者一除,就得到了我们开篇提到的神奇函数 g

所以原式就变成了:

T=1min(a,b)g(T)aTbT

预处理 g 的前缀积和其逆元,这个就可以一层数论分块搞定!!!

type=0

f(0)=1 带入并稍做处理,得:

(i=1Aj=1Bij)C(i=1Aj=1Bgcd(i,j))C(i=1Aj=1Cgcd(i,j))B

1.分子

i 提出来(求乘积,故加上 B 次方):

(i=1A(iBj=1Bj))C

发现内层循环就是 B! ,即:

(i=1A(iBB!))C

B!i 无关,于是分离开来(记得加上 A 次方):

((i=1AiB)(B!)A)C

观察左边的式子,由于是求乘积,可以把 B 次方提出来,之后里面就成了 A! ,即:

((A!)B(B!)A)C

很优美了,预处理阶乘后快速幂就行。

2.分母

发现两项非常相似,于是我们令:

f0(a,b,c)=(i=1aj=1bgcd(i,j))c

此时分母就成了:

f0(A,B,C)×f0(A,C,B)

f0 里面的东西开篇就推过了,所以有:

f0=T=1min(a,b)g(T)adkbdkc

虽然我有统统带回去的毛病,但懒了,不把原式子放出来了!

这样这部分就做完了!

type=1

f(1)=ijk 带入,得:

i=1Aj=1Bk=1C(ij)ijk(i=1Aj=1Bk=1Cgcd(i,j)ijk)(i=1Aj=1Bk=1Cgcd(i,k)ijk)

分子

把关于 k 的一维扔出去:

k=1C(i=1Aj=1B(ij)ij)k

发现在 k 变化时,内部不变,且外层循环的含义就是 k=1Ck 个内部的东西乘起来,于是可以变形:

(i=1Aj=1B(ij)ij)k=1Ck

求和公式套进去:

(i=1Aj=1B(ij)ij)C(C+1)2

接下来,我们先处理内部:

i=1Aj=1B(ij)ij

把幂放进去:

i=1Aj=1Biijjij

iijjij 分开计算(为了使结构更相似,将后面那部分把 ij 互换名称):

(i=1Aj=1Biij)(i=1Bj=1Aiij)

两部分结构又相同了,我们考虑其中一个:

i=1Aj=1Biij

可以发现,在 j 变化时,ii 不变,这还是求乘积,可以把关于 j 的一维扔到指数上:

i=1A(ii)j=1Bj

指数就成了求和公式,这玩意我们熟悉的不得了,于是:

i=1A(ii)B(B+1)2

然后一层层往回带,分子部分就变成了:

((i=1Aii)B(B+1)2(i=1Bii)A(A+1)2)C(C+1)2

预处理 ii 的前缀和,直接算就行了。

分母

发现两项非常相似,于是我们令:

f1(a,b,c)=i=1aj=1bk=1cgcd(i,j)ijk

此时分母就成了:

f1(A,B,C)×f1(A,C,B)

接下来处理 f1,先把无关紧要的 k 提出去,通过和处理分子相同的方法,发现变成了:

(i=1aj=1bgcd(i,j)ij)c(c+1)2

接下来套路处理内部就行了,枚举 gcd

(d=1min(a,b)di=1adi=1bd[gcd(i,j)=1]ijd2)c(c+1)2

莫反拆开,枚举 k

(d=1min(a,b)(dd2)k=1min(ad,bd)μ(k)k2i=1adki=1bdkij)c(c+1)2

ij 的循环拆开,发现是等差数列求和,于是:

(d=1min(a,b)(dd2)k=1min(ad,bd)μ(k)k2adk(adk+1)2bdk(bdk+1)2)c(c+1)2

预处理一下 dd2 的前缀积,和 μ(k)k2 的前缀和,两层数论分块做就完事了。但是,我们有更优的做法!

继续推,把指数上的求和放下来,有:

(d=1min(a,b)k=1min(ad,bd)dd2μ(k)k2adk(adk+1)2bdk(bdk+1)2)c(c+1)2

枚举 T=dk,有:

T=1min(a,b)(k|T(Tk)(Tk)2μ(k)k2)aT(aT+1)2bT(bT+1)2c(c+1)2

也就是:

T=1min(a,b)(k|T(Tk)μ(k)T2)aT(aT+1)2bT(bT+1)2c(c+1)2

我们找到老熟人了,里面的东西其实就是 g(T)T2

于是我们预处理这个东西的前缀积和其逆元,就有:

T=1min(a,b)g(T)T2aT(aT+1)2bT(bT+1)2c(c+1)2

一次数论分块即可。

type=2

f(2)=gcd(i,j,k) 带入,得:

i=1Aj=1Bk=1C(ij)gcd(i,j,k)(i=1Aj=1Bk=1Cgcd(i,j)gcd(i,j,k))(i=1Aj=1Bk=1Cgcd(i,k)gcd(i,j,k))

我们处理分子分母吧……好累啊……

分子

我们记:

f2(a,b,c)=i=1aj=1bk=1cigcd(i,j,k)

则分子就是 f2(a,b,c)×f2(b,a,c)

我们推 f2

大头敲开方向要对鲜美鸡汤必须喝掉

枚举gcd莫反拆开枚举dk后数论分块

d=1min(a,b,c)i=1adj=1bdk=1cd(id)d[gcd(i,j,k)=1]

d=1min(a,b,c)i=1adj=1bdk=1cdt|gcd(i,j,k)(id)dμ(t)

d=1min(a,b,c)t=1min(ad,bd,cd)i=1adtj=1bdtk=1cdt(idt)dμ(t)

T=1min(a,b,c)i=1aTj=1bTk=1cT(iT)d|Tdμ(Td)

蛮好的,有 φ 了:

T=1min(a,b,c)i=1aTj=1bTk=1cT(iT)φ(T)

iT 分开,有:

T=1min(a,b,c)Tφ(T)aTbTcT(aT!)bTcTφ(T)

一次数论分块即可。

分母

我们令:

f3(a,b,c)=i=1aj=1bk=1cgcd(i,j)gcd(i,j,k)

那么分母就是:f3(A,B,C)×f3(A,C,B)

f3 即可,这东西折磨了我好久,直到我把两次拆开放到一起……

枚举 d=gcd(i,j,k),有:

d=1min(a,b,c)i=1adj=1bdk=1cd(dgcd(i,j))d[gcd(i,j,k)=1]

拆成 μ,放到底数:

d=1min(a,b,c)i=1adj=1bdk=1cdt|gcd(i,j,k)(dgcd(i,j))dμ(t)

t 移出去:

d=1min(a,b,c)t=1min(ad,bd,cd)i=1adtj=1bdtk=1cdt(dtgcd(i,j))dμ(t)

发现 k 这一维可以缩水到指数上了,顺便把变量名 t 换成 k,有:

d=1min(a,b,c)k=1min(ad,bd,cd)i=1adkj=1bdk(dkgcd(i,j))dμ(k)cdk

然后我们枚举 T=dk,有:

T=1min(a,b,c)i=1aTj=1bT(Tgcd(i,j))cdkd|Tdμ(Td)

得,φ 出来了,这一步我退出来乐死了

T=1min(a,b,c)i=1aTj=1bT(Tgcd(i,j))cdkφ(T)

分成两部分,就是:

T=1min(a,b,c)Tφ(T)aTbT(i=1aTj=1bTgcd(i,j))cdkφ(T)

后者,又出现了我们文章最开头推的式子,于是就成了:

T=1min(a,b,c)Tφ(T)aTbT(D=1min(aT,bT)g(D)aDTaDT)cdkφ(T)

两层数论分块,终于,搞!定!

这b题终于让我写!!完!!了!!

提交记录

点击查看代码
//#pragma GCC optimize(2)
//#pragma GCC optimize(3)
//#pragma GCC optimize("Ofast","inline")
#include<bits/stdc++.h>
#define fr(a) freopen(a,"r",stdin)
#define fw(a) freopen(a,"w",stdout)
#define MP(a,b) make_pair(a,b)
#define DEBUG
using namespace std;
typedef long long ll;
const int N=1e5+10;
ll T=1,mod,pmod;
ll A,B,C,ans1,ans2,ans3;
ll prime[N],v[N],tot,mu[N],smu[N],smu2[N],phi[N];
ll jie[N],invj[N],tphi[N],invtphi[N],sphi[N];
ll slf[N],invslf[N];
ll gm[N],invgm[N],invsmu[N];
ll nai[N],sn[N],invsn[N],sn2[N],invsn2[N];
ll qpow(ll a,ll n){
	n=(n%pmod +pmod)%pmod;
	a %=mod;
	ll res=1;
	while(n){
		if(n&1) res=(res*a)%mod;
		a=(a*a)%mod;n>>=1;
	}return res;
}
void shai(ll n){
	mu[1]=phi[1]=1;
	for(ll i=2;i<=n;i++){
		if(v[i]==0){
			v[i]=i;prime[++tot]=i;
			mu[i]=-1;phi[i]=i-1;
		}for(ll j=1;j<=tot;j++){
			if(prime[j]>v[i]||prime[j]>n/i) break;
			v[i*prime[j]]=prime[j];
			if(i%prime[j]==0) mu[i*prime[j]]=0,phi[i*prime[j]]=phi[i]*(prime[j]);
			else mu[i*prime[j]]=-mu[i],phi[i*prime[j]]=phi[i]*(prime[j]-1);
		}
	}
}
void init(ll n){
	jie[0]=invj[0]=slf[0]=invslf[0]=1;
	gm[0]=invgm[0]=1;
	tphi[0]=invtphi[0]=1;
	pmod=mod-1;
	for(ll i=1;i<=n;i++){
		jie[i]=(jie[i-1]*i)%mod;
		invj[i]=qpow(jie[i],mod-2);
		slf[i]=slf[i-1] * qpow(i,i)%mod;
		invslf[i]=qpow(slf[i],mod-2);
		smu[i]=(smu[i-1]+mu[i])%pmod;
		smu2[i]=(smu2[i-1] + mu[i] * i * i%pmod)%pmod;
		gm[i]=gm[i-1] * qpow(i,mu[i])%mod;
		invgm[i]=qpow(gm[i],-1);
		tphi[i]=tphi[i-1] * qpow(i,phi[i]) %mod;
		invtphi[i]=qpow(tphi[i],-1);
		sphi[i] = (sphi[i-1] + phi[i]) %pmod;
		nai[i]=1;
	}
	for(int i=1;i<=tot;i++){
		ll now=prime[i];
		if(now>n) break;
		while(now<n){
			nai[now]=prime[i];
			now *= prime[i];
		}
	}
	sn[0]=invsn[0]=sn2[0]=invsn2[0]=1;
	for(int i=1;i<=n;i++){
		sn[i]=(sn[i-1] * nai[i])%mod;
		invsn[i]=qpow(sn[i],-1);
		sn2[i]= sn2[i-1] * qpow(nai[i],1ll * i*i%pmod) %mod;
		invsn2[i]=qpow(sn2[i],-1);
	}
}
ll f32(ll a,ll b){
	ll res=1,lim=min(a,b);
	for(ll l=1,r;l<=lim;l=r+1){
		ll ad=a/l,bd=b/l;r=min(a/ad,b/bd);
		(res *= qpow(sn[r] * invsn[l-1] %mod ,ad * bd%pmod) )%=mod;
	}return res;
}
ll f0(ll a,ll b,ll c){
	return qpow(f32(a,b),c);
}
void work1(){	
	ll ans=qpow(qpow(jie[A],B)*qpow(jie[B],A)%mod,C),tot=1;
	(ans*=qpow(f0(A,B,C) * f0(A,C,B) %mod,mod-2)) %=mod;
	ans1=(ans+mod)%mod;
}
ll f1(ll a,ll b,ll c){
	ll res=1,lim=min(a,b);
	for(ll l=1,r;l<=lim;l=r+1){
		ll ad=a/l,bd=b/l;r=min(a/ad,b/bd);
		ll zhi=(ad*(ad+1)/2) %pmod *( (bd*(bd+1)/2) %pmod) %pmod;
		(res *= qpow(sn2[r] * invsn2[l-1] %mod ,zhi) )%=mod;
	}
	res= qpow(res,c*(c+1)/2);
	return res;
}
void work2(){
	ll zi=1,ans=qpow(f1(A,B,C) * f1(A,C,B)%mod,mod-2);
	zi=qpow(slf[A],B*(B+1)/2) * qpow(slf[B],A*(A+1)/2) %mod;
	(ans *=qpow(zi,C*(C+1)/2)) %=mod;
	ans2=(ans+mod)%mod;
}
ll f2(ll a,ll b,ll c){
	ll res=1,limd=min(min(a,b),c);
	for(ll ld=1,rd;ld<=limd;ld=rd+1){
		ll ad=a/ld,bd=b/ld,cd=c/ld;
		rd=min(min(a/ad,b/bd),c/cd);
		(res *= qpow(gm[rd] * invgm[ld-1]%mod,ad*bd%pmod*cd%pmod) )%=mod;
		ll tmp=qpow(jie[ad],bd*cd%pmod);
		(res *= qpow(tmp,(smu[rd]-smu[ld-1])%pmod))%=mod;
	}return res;
}
ll f2_5(ll a,ll b,ll c){
	ll res=1,lim=min(min(a,b),c);
	for(ll l=1,r;l<=lim;l=r+1){
		ll at=a/l,bt=b/l,ct=c/l;
		r=min(min(a/at,b/bt),c/ct);
		(res *= qpow(tphi[r] * invtphi[l-1]%mod , at * bt %pmod *ct %pmod))%=mod;
		ll tmp=qpow(jie[at],bt*ct%pmod);
		(res *= qpow(tmp,(sphi[r] - sphi[l-1]) %pmod))%=mod;
	}return res;
}
ll f3(ll a,ll b,ll c){
	ll limt=min(min(a,b),c),res=1;
	for(int lt=1,rt;lt<=limt;lt=rt+1){
		ll at=a/lt,bt=b/lt,ct=c/lt;
		rt=min(min(a/at,b/bt),c/ct);
		ll tmp=tphi[rt] * invtphi[lt-1] %mod;
		(res *= qpow(tmp,at * bt%pmod * ct%pmod) )%=mod;
		tmp=qpow(f32(at,bt) %mod,ct) %mod;
		(res *= qpow(tmp,(sphi[rt] - sphi[lt-1]) %pmod) )%=mod;
	}return res;
}
void work3(){
	ll zi=f2_5(A,B,C) * f2_5(B,A,C) %mod;
	ll mu=f3(A,B,C) * f3(A,C,B)%mod;
	ans3=(zi*qpow(mu,-1)%mod+mod)%mod;
	return;
}
int main(){
	scanf("%lld%lld",&T,&mod);
	shai(N-10);
	init(N-10);
	for(int Case=1;Case<=T;Case++){
		scanf("%lld%lld%lld",&A,&B,&C);
		work1();work2();
		work3();
		printf("%lld %lld %lld\n",ans1,ans2,ans3);
	}
	return 0;
}


- P1587 [NOI2016] 循环之美

给定 n,m,k,求分子不大于 n,分母不大于 m,在 k 进制下为纯循环小数的最简分数个数。

我们有一个不会证的结论:当分母和进制互质,分子和分母互质时,这个分数是 k 进制下的纯循环小数。

于是就成了求以下东西:

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

推式子,对后一项莫反拆开:

d|kμ(d)i=1nj=1md[gcd(i,jd)=1]

显然,我们有 gcd(a,b)=1,gcd(a,c)=1gcd(a,bc)=1

故我们发现后面是一个规模缩小的原问题。

我们设:

f(n,m,k)=i=1nj=1m[gcd(i,j)=1][gcd(j,k)=1]

于是我们有递推式:

f(n,m,k)=d|kμ(d)f(md,n,d)

直接开搜就行了。

终止状态:

  1. n=0m=0 时,f(n,m,k)=0

  2. k=1 时,f(n,m,k)=d=1min(n,m)μ(d)ndmd,基本推式子没的说。

所以我们用杜教筛处理 μ 前缀和,数论分块算上面的东西,并加入以下剪枝:

  1. 我们提前预处理 k 的“所有因数”的所有因数,显然空间复杂度是 O(k2) 的,可以接受,此时枚举 d 的复杂度直接从 1000 降到了 64,很大的优化。

  2. μ(d)=0 时,不往下搜,显然,这个优化更大。

加入这两个剪枝后就能顺利过此题了。

提交记录

特别鸣鸣鸣鸣鸣鸣鸣鸣谢 Qcfff,基本上思路全都是抄他的呜呜呜。

P3704 [SDOI2017] 数字表格

给定 n,m,求:

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

这不乱做?

d=1min(n,m)k=1min(nd,md)fdμ(k)ndkmdk

枚举 T=dk

T=1min(n,m)(dTfdμ(Td))nTmT

里面的东西预处理,O(nn),因为指数只会是 0,1,1

然后做完了!整除分块。水紫真舒服

提交记录

P2257 YY的GCD

求:

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

莫个反先:

pPd=1min(np,mp)μ(d)npdmpd

枚举 T=pd

T=1min(n,m)nTmTpP,pTμ(Tp)

后面的可以预处理。求个前缀和。做完了。

提交记录

双倍经验 & 提交记录

P1891 疯狂 LCM

挺好玩。求:

i=1nlcm(i,n)

先拆一下:

i=1ni×ngcd(i,n)

枚举 gcd

ndn1di=1nddi[gcd(i,nd)=1]

发现好玩的事:d 约掉了。我们记 f(n)=i=1ni[gcd(i,n)=1],于是原式变成:

ndnf(nd)=ndnf(d)

我们先处理 f(n):莫反拆开:

f(n)=i=1nikgcd(i,n)μ(k)

枚举 k

f(n)=knμ(k)i=1nkik

也就是:

f(n)=knμ(k)knk(nk+1)2=knμ(k)n(nk+1)12

然后 f 可以 O(nlogn) 预处理吧!我们再看原式:

ndnf(d)

也可以 O(nlogn) 预处理吧!

于是我们 O(nlogn) 预处理,O(1) 查询。做完了!

提交记录

posted @   一匹大宝羊  阅读(49)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 张高兴的大模型开发实战:(一)使用 Selenium 进行网页爬虫
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示