「学习笔记」莫比乌斯反演

更熟悉的阅读体验?

前置芝士:数论分块。(之后再说。QWQ)

积性函数

定义

一个数论函数 f(n) 满足 f(xy)=f(x)×f(y)gcd(x,y)=1),则称 f(n) 是积性函数。

莫比乌斯函数:

μ(n)={1n=10n 含有平方因子(1)kk  n 的本质不同质因子个数

杜教筛

杜教筛代码真是好短啊。

一种时间复杂度为 O(n23) 的筛,可以求出一些积性函数的前缀和。

具体来说,求 S(n)=i=1nf(i)f 是积性函数。

构造函数 h,g 满足 h=fg

那么:

i=1nh(i)=i=1nd|if(i)g(ni)=d=1ng(d)i=1ndf(i)=d=1ng(d)S(nd)

那么:

g(1)S(n)=i=1nh(i)d=2ng(d)S(nd)

(这里就是把 d=1 的给提出来了)

S(n)=i=1nh(i)d=2ng(d)S(nd)g(1)

对于 μ 函数,知道 μI=ϵ,而 ϵμ 的前缀和都很好求,所以可以用杜教筛。

而对于 φ 函数,知道 φI=id,也可以用杜教筛。

P4213 【模板】杜教筛(Sum)

点击查看代码
#include<bits/stdc++.h> #define XD 114514 #define MAXN 10000010 #define int long long using namespace std; int T,n; int phi[MAXN],mu[MAXN],sumphi[MAXN],summu[MAXN]; bool vis[MAXN]; vector<int> prm; map<int,int> mphi,mmu; void sieve(){ mu[1]=phi[1]=sumphi[1]=summu[1]=1; for(int i=2;i<=1e7;i++){ if(!vis[i]){ prm.push_back(i); phi[i]=i-1;mu[i]=-1; } sumphi[i]=sumphi[i-1]+phi[i]; summu[i]=summu[i-1]+mu[i]; for(auto j:prm){ if(i*j>1e7) break; vis[i*j]=true; if(i%j==0){ phi[i*j]=phi[i]*j; break; } phi[i*j]=phi[i]*phi[j]; mu[i*j]=-mu[i]; } } } int getmu(int x){ if(x<=1e7) return summu[x]; if(mmu[x]) return mmu[x]; int nem=1,r; for(int l=2;l<=x;l=r+1){ r=x/(x/l); nem-=(r-l+1)*getmu(x/l); } return mmu[x]=nem; } int getphi(int x){ if(x<=1e7) return sumphi[x]; if(mphi[x]) return mphi[x]; int nem=x*(x+1)/2,r; for(int l=2;l<=x;l=r+1){ r=x/(x/l); nem-=(r-l+1)*getphi(x/l); } return mphi[x]=nem; } signed main(){ cin>>T; sieve(); while(T--){ cin>>n; cout<<getphi(n)<<" "<<getmu(n)<<"\n"; } return 0; }

莫比乌斯反演

P2398 GCD SUM

GCDMAT - GCD OF MATRIX 双倍经验)

求:

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

运用欧拉反演,可得:

i=1nj=1nd|gcd(i,j)φ(d)

转换,得:

i=1nj=1nd=1nφ(d)[d|i][d|j]

d=1nφ(d)i=1n[d|i]j=1n[d|j]

d=1nφ(d)nd2

线性欧拉筛即可。(可以用数论分块,也可以不用,因为 n 范围太小了)

不用数论分块:

点击查看代码
#include<bits/stdc++.h> #define XD 114514 #define MAXN 100010 #define int long long using namespace std; int ans; int n,phi[MAXN]; vector<int> prm; bool vis[MAXN]; void prime(){ phi[1]=1; for(int i=2;i<=n;i++){ if(!vis[i]){ prm.push_back(i); phi[i]=i-1; } for(int j=0;j<prm.size();j++){ if(i*prm[j]<=n){ int nem=i*prm[j]; vis[nem]=true; if(i%prm[j]==0){ phi[nem]=phi[i]*prm[j]; break; }else{ phi[nem]=phi[i]*phi[prm[j]]; } }else break; } } } signed main(){ cin>>n; prime(); for(int i=1;i<=n;i++){ ans+=phi[i]*(n/i)*(n/i); } cout<<ans; return 0; }

用数论分块:

点击查看代码
#include<bits/stdc++.h> #define XD 114514 #define MAXN 100010 #define int long long using namespace std; int ans; int n,sum[MAXN],phi[MAXN]; vector<int> prm; bool vis[MAXN]; void prime(){ sum[1]=phi[1]=1; for(int i=2;i<=n;i++){ if(!vis[i]){ prm.push_back(i); phi[i]=i-1; } for(int j=0;j<prm.size();j++){ if(i*prm[j]<=n){ int nem=i*prm[j]; vis[nem]=true; if(i%prm[j]==0){ phi[nem]=phi[i]*prm[j]; break; }else{ phi[nem]=phi[i]*phi[prm[j]]; } }else break; } sum[i]=sum[i-1]+phi[i]; } } signed main(){ cin>>n; prime(); int r; for(int l=1;l<=n;l=r+1){ int nem=n/l; r=n/nem; ans+=(sum[r]-sum[l-1])*(n/l)*(n/l); } cout<<ans; return 0; }

P2303 [SDOI2012] Longge 的问题

i=1ngcd(i,n)

k|nki=1n[gcd(i,n)=k]

k|nki=1n[gcd(ik,nk)=1]

k|nki=1nk[gcd(i,nk)=1]

k|nk×φ(nk)

单次求欧拉函数即可。

点击查看代码
#include<bits/stdc++.h> #define XD 114514 #define int long long using namespace std; int n,ans; int phi(int x){ int num=x; for(int i=2;i*i<=x;i++){ if(x%i==0) num=num/i*(i-1); while(x%i==0) x/=i; } if(x>1) num=num/x*(x-1); return num; } signed main(){ cin>>n; for(int i=1;i*i<=n;i++){ if(i*i==n) ans+=i*phi(n/i); else if(n%i==0) ans+=i*phi(n/i)+(n/i)*phi(i); } cout<<ans; return 0; }

P1390 公约数的和

UVA11417 GCD / UVA11424 GCD - Extreme (I) /P1390 公约数的和/ SP3871 GCDEX - GCD Extreme /UVA11426 拿行李(极限版) GCD - Extreme (II) (5 倍经验,按数据范围从小到大排序)

求:

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

转化为:

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

后面的直接套用上一道题的式子,可以提前线性筛欧拉函数,然后提前把答案都求出来,这样就可以把前两倍经验给切了。QWQ

点击查看代码
#include<bits/stdc++.h> #define XD 114514 #define MAXN 200010 using namespace std; int n; bool vis[MAXN]; int phi[MAXN]; vector<int> prm; void sieve(){ phi[1]=1; for(int i=2;i<=2e5+1;i++){ if(!vis[i]){ prm.push_back(i); phi[i]=i-1; } for(int j=0;j<prm.size() and i*prm[j]<=2e5+1;j++){ vis[i*prm[j]]=true; if(i%prm[j]==0){ phi[i*prm[j]]=phi[i]*prm[j]; break; } phi[i*prm[j]]=phi[i]*phi[prm[j]]; } } } long long ans[MAXN],num; int main(){ ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); sieve(); for(int i=1;i<=2e5+1;i++){ for(int j=1;j*j<=i;j++){ if(j*j==i) num+=j*phi[j]; else if(i%j==0) num+=1ll*j*phi[i/j]+(i/j)*phi[j]; } num-=i; ans[i]=num; } while(true){ cin>>n;if(n==0) break; cout<<ans[n]<<"\n"; } return 0; }

时间复杂度预处理 O(nn),查询 O(1)

考虑优化。

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

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

i=1nk|ik×φ(ik)(n+1)×n2

看左面的式子。

i=1nk=1nk×φ(ik)[k|i]

k=1nki=1nφ(ik)[k|i]

k=1nki=1nkφ(i)

还是预处理线性筛欧拉函数,查询直接数论分块即可。

在代码里,为防止爆 long long,我把要减的 (n+1)×n2 直接在数论分块中算了。QWQ

点击查看代码
#include<bits/stdc++.h> #define XD 114514 #define MAXN 4000010 using namespace std; int n; bool vis[MAXN]; int phi[MAXN]; long long num[MAXN]; vector<int> prm; void sieve(){ phi[1]=1;num[1]=1; for(int i=2;i<=4e6;i++){ if(!vis[i]){ prm.push_back(i); phi[i]=i-1; } num[i]=num[i-1]+phi[i]; for(int j=0;j<prm.size() and i*prm[j]<=4e6;j++){ vis[i*prm[j]]=true; if(i%prm[j]==0){ phi[i*prm[j]]=phi[i]*prm[j]; break; } phi[i*prm[j]]=phi[i]*phi[prm[j]]; } } } long long ans; int main(){ ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); sieve(); while(true){ ans=0; cin>>n; if(n==0) break; int r; for(int l=1;l<=n;l=r+1){ r=n/(n/l); ans+=(1ll*(r+1)*r/2-1ll*(l-1)*l/2)*(1ll*num[n/l]-1); } cout<<ans<<"\n"; } return 0; }

说句闲话,这篇题解的式子(是我上面推的式子的方法)有点问题。QWQ

膜运算

这是一个裸题,因为显然可以用欧几里得定理搞成原题。

而我才不会告诉你我想的有亿点复杂。

推式子

P3455 [POI2007] ZAP-Queries

P4450 双亲数 双倍经验)

求:

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

经典把 gcd(i,j)=k 转化为 gcd(i,j)=1

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

i=1nkj=1mk[gcd(i,j)=1]

i=1nkj=1mkd|gcd(i,j)μ(d)

i=1nkj=1mkd|i,d|jμ(d)

i=1nkj=1mkd=1min(nk,mk)μ(d)[d|i][d|j]

d=1min(nk,mk)μ(d)i=1nk[d|i]j=1mk[d|j]

d=1min(nk,mk)μ(d)nkdmkd

线性筛一遍莫比乌斯函数再用数论分块即可。

点击查看代码
#include<bits/stdc++.h> #define XD 114514 #define MAXN 50010 using namespace std; int t,n,m,a,b,k,ans; int mu[MAXN],num[MAXN]; bool vis[MAXN]; vector<int> prm; void sieve(){ mu[1]=1;num[1]=1; for(int i=2;i<=5e4;i++){ if(!vis[i]){ prm.push_back(i); mu[i]=-1; } num[i]=num[i-1]+mu[i]; for(int j=0;j<prm.size() and i*prm[j]<=5e4;j++){ vis[i*prm[j]]=true; if(i%prm[j]==0) break; mu[i*prm[j]]=-mu[i]; } } } void calc(){ int r; for(int l=1;l<=a;l=r+1){ r=min(a/(a/l),b/(b/l)); ans+=(num[r]-num[l-1])*(a/l)*(b/l); } } int main(){ cin>>t; sieve(); while(t--){ ans=0; cin>>n>>m>>k; a=n/k;b=m/k; if(a>b) swap(a,b); calc(); cout<<ans<<"\n"; } return 0; }

P2522 [HAOI2011] Problem b

就是用容斥原理搞一下,之后就和上面的一样了。

点击查看代码
#include<bits/stdc++.h> #define XD 114514 #define MAXN 50010 using namespace std; int t,n,m,a,b,c,d,k; int mu[MAXN],num[MAXN]; bool vis[MAXN]; vector<int> prm; void sieve(){ mu[1]=1;num[1]=1; for(int i=2;i<=5e4;i++){ if(!vis[i]){ prm.push_back(i); mu[i]=-1; } num[i]=num[i-1]+mu[i]; for(int j=0;j<prm.size() and i*prm[j]<=5e4;j++){ vis[i*prm[j]]=true; if(i%prm[j]==0) break; mu[i*prm[j]]=-mu[i]; } } } int calc(int x,int y){ n=x/k;m=y/k; if(n>m) swap(n,m); int r,ans=0; for(int l=1;l<=n;l=r+1){ r=min(n/(n/l),m/(m/l)); ans+=(num[r]-num[l-1])*(n/l)*(m/l); } return ans; } int main(){ sieve(); cin>>t; while(t--){ cin>>a>>b>>c>>d>>k; cout<<calc(b,d)-calc(a-1,d)-calc(b,c-1)+calc(a-1,c-1)<<"\n"; } return 0; }

P2257 YY的GCD PGCD - Primes in GCD Table

下面正式推式子。

求:

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

经典把 gcd(i,j)=k 转化为 gcd(i,j)=1

i=1nj=1mpprime,p|i,p|j[gcd(ip,jp)=1]

i=1nj=1mpprime[gcd(ip,jp)=1][p|i][p|j]

pprimei=1npj=1mp[gcd(i,j)=1]

然后用莫比乌斯反演。

pprimei=1npj=1mpd|gcd(i,j)μ(d)

pprimei=1npj=1mpd|i,d|jμ(d)

pprimei=1npj=1mpd=1min(np,mp)μ(d)[d|i][d|j]

这里就把 n 当做 min(n,m)

pprimei=1npj=1mpd=1npμ(d)[d|i][d|j]

pprimed=1npμ(d)i=1np[d|i]j=1mp[d|j]

pprimed=1npμ(d)npdmpd

我们设 T=pd,并将式子转换成枚举 T

T=1nnTmTpprime,p|Tμ(Tp)

可以发现右面的式子可以提前求出。左面的式子数论分块即可。

点击查看代码
#include<bits/stdc++.h> #define XD 114514 #define MAXN 10000010 using namespace std; int t,n,m; int mu[MAXN],f[MAXN],sum[MAXN]; bool vis[MAXN]; vector<int> prm; void sieve(){ mu[1]=1; for(int i=2;i<=1e7;i++){ if(!vis[i]){ prm.push_back(i); mu[i]=-1; } for(int j=0;j<prm.size();j++){ if(i*prm[j]>1e7) break; vis[i*prm[j]]=true; if(i%prm[j]==0) break; mu[i*prm[j]]=-mu[i]; } } for(int i=0;i<prm.size();i++){ for(int j=1;j<=1e7;j++){ if(prm[i]*j>1e7) break; f[prm[i]*j]+=mu[j]; } } for(int i=1;i<=1e7;i++) sum[i]=sum[i-1]+f[i]; } long long ans; void solve(){ int r=0;if(n>m) swap(n,m); for(int l=1;l<=n;l=r+1){ int nem1=n/l,nem2=m/l; r=min(n/nem1,m/nem2); ans+=1ll*(sum[r]-sum[l-1])*(n/l)*(m/l); } } int main(){ ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); cin>>t; sieve(); while(t--){ ans=0; cin>>n>>m; solve(); cout<<ans<<"\n"; } return 0; }

P3327 [SDOI2015] 约数个数和

求:

i=1nj=1md(ij)

首先,要知道 d 函数的一个性质:

d(ij)=x|iy|j[gcd(x,y)=1]

所以原式就变成了:

i=1nj=1mx|iy|j[gcd(x,y)=1]

x=1nnxy=1mmy[gcd(x,y)=1]

x=1nnxy=1mmyd=1min(n,m)μ(d)[d|x][d|y]

d=1min(n,m)μ(d)x=1ndnxdy=1mdmyd

p=ndq=md,则:

d=1min(n,m)μ(d)x=1ppxy=1qqy

可以发现 x=1ppxy=1qqy 可以预处理时存在数组里,我们设 f(x)=i=1xxi

d=1min(n,m)μ(d)f(p)f(q)

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

于是查询时数论分块即可。(相当于数论分块套数论分块 QWQ)

点击查看代码
#include<bits/stdc++.h> #define XD 114514 #define MAXN 50010 #define int long long using namespace std; int T,n,m,ans; bool vis[MAXN]; int mu[MAXN],sum[MAXN],num[MAXN]; vector<int> prm; void sieve(){ mu[1]=1;sum[1]=1; for(int i=2;i<=5e4;i++){ if(!vis[i]){ prm.push_back(i); mu[i]=-1; } for(int j=0;j<prm.size() and prm[j]*i<=5e4;j++){ vis[i*prm[j]]=true; if(i%prm[j]==0) break; mu[i*prm[j]]=-mu[i]; } sum[i]=sum[i-1]+mu[i]; } for(int i=1;i<=5e4;i++){ int r,nem=0; for(int l=1;l<=i;l=r+1){ r=i/(i/l); nem+=(r-l+1)*(i/l); } num[i]=nem; } } void calc(){ int r; for(int l=1;l<=n;l=r+1){ r=min(n/(n/l),m/(m/l)); ans+=(sum[r]-sum[l-1])*num[n/l]*num[m/l]; } } signed main(){ cin>>T; sieve(); while(T--){ ans=0; cin>>n>>m; if(n>m) swap(n,m); calc(); cout<<ans<<"\n"; } return 0; }

P3768 简单的数学题

求:

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

i=1nj=1nijd=1nφ(d)[d|i][d|j]

d=1nφ(d)i=1ni[d|i]j=1nj[d|j]

d=1nφ(d)i=1ndi×dj=1ndj×d

为方便,设 m=nd

d=1nφ(d)d2×m2(m+1)24

φ(n)n2 这个东西要用杜教筛。

f=φ×id×idg=id×id,则:

(hg)(n)=d|nf(d)g(nd)=d|nφ(d)×d2×(nd)2=n2×d|nφ(d)=n3

所以用杜教筛后数论分块即可。

点击查看代码
#include<bits/stdc++.h> #define XD 114514 #define MAXN 5000010 #define int long long using namespace std; int p,n,ans; int inv4,inv6; int phi[MAXN],sumphi[MAXN]; bool vis[MAXN]; vector<int> prm; void sieve(){ phi[1]=sumphi[1]=1; for(int i=2;i<=5e6;i++){ if(!vis[i]){ prm.push_back(i); phi[i]=i-1; } sumphi[i]=(sumphi[i-1]+phi[i]*i%p*i%p)%p; for(auto j:prm){ if(i*j>5e6) break; vis[i*j]=true; if(i%j==0){ phi[i*j]=phi[i]*j; break; } phi[i*j]=phi[i]*phi[j]; } } } int power(int x,int y){ int nem=1; while(x){ if(x&1) nem=(nem*y)%p; y=(y*y)%p;x>>=1; } return nem; } int pow2(int x){ x%=p;return x*(x+1)%p*(2*x+1)%p*inv6%p; } int pow3(int x){ x%=p;return x*x%p*(x+1)%p*(x+1)%p*inv4%p; } map<int,int> mphi; int getphi(int x){ if(x<=5e6) return sumphi[x]; if(mphi[x]) return mphi[x]; int nem=pow3(x),r; for(int l=2;l<=x;l=r+1){ r=x/(x/l); nem=(nem-((pow2(r)-pow2(l-1)+p)%p*getphi(x/l)%p)%p+p)%p; } return mphi[x]=(nem%p+p)%p; } signed main(){ cin>>p>>n; sieve(); inv4=power(p-2,4); inv6=power(p-2,6); int r; for(int l=1;l<=n;l=r+1){ r=n/(n/l); ans=(ans+((getphi(r)-getphi(l-1))%p+p)%p*pow3(n/l))%p; } cout<<(ans%p+p)%p; return 0; }

P1891 疯狂 LCM LCMSUM - LCM Sum

i=1nlcm(n,i)

i=1nd|nn×id[gcd(n,i)=d]

nd|ni=1ndi[gcd(i,nd)=1]

有一个式子: i=1ni[gcd(n,i)=1]=φ(n)n2

nd|nφ(d)d2

提前求出 d|nφ(d)d2 的值即可。(时间复杂度 O(nlnn)

点击查看代码
#include<bits/stdc++.h> #define XD 114514 #define MAXN 1000010 #define ll long long using namespace std; int t,n; int phi[MAXN]; ll ans[MAXN]; vector<int> prm;bool vis[MAXN]; void sieve(){ phi[1]=1; for(int i=2;i<=1e6;i++){ if(!vis[i]) prm.push_back(i),phi[i]=i-1; for(auto j:prm){ if(i*j>1e6) break; vis[i*j]=true; if(i%j==0){ phi[i*j]=phi[i]*j; break; } phi[i*j]=phi[i]*phi[j]; } } for(int d=1;d<=1e6;d++){ for(int i=d;i<=1e6;i+=d){ if(d==1){ans[i]++;continue;} ans[i]+=1ll*phi[d]*d/2; } } } int main(){ ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); sieve(); cin>>t; while(t--){ cin>>n; cout<<1ll*n*ans[n]<<"\n"; } return 0; }

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

n<m。求:

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

i=1nj=1md=1ni×jd[gcd(i,j)=d]

为方便,设 x=ndy=md

d=1ni=1xj=1yd×i×j[gcd(i,j)=1]

d=1ndi=1xj=1yi×jk|i,k|jμ(k)

d=1ndk=1xμ(k)×k×ki=1xdij=1ydj

sum(x)=i=1xif(x,y)=k=1xμ(k)×k×k×sum(xd)×sum(yd)

d=1nd×f(x,y)

数论分块套数论分块即可。

点击查看代码
#include<bits/stdc++.h> #define XD 114514 #define MAXN 10000010 #define int long long using namespace std; const int mod=20101009; int t,n,m,ans; int mu[MAXN],num[MAXN]; bool vis[MAXN]; vector<int> prm; void sieve(){ mu[1]=1;num[1]=1; for(int i=2;i<=1e7;i++){ if(!vis[i]){ prm.push_back(i); mu[i]=-1; } num[i]=(num[i-1]+mu[i]*i%mod*i%mod)%mod; for(int j=0;j<prm.size() and i*prm[j]<=1e7;j++){ vis[i*prm[j]]=true; if(i%prm[j]==0) break; mu[i*prm[j]]=-mu[i]; } } } int getnum(int l,int r){return ((r+1)*r/2%mod-(l-1)*l/2%mod+mod)%mod;} int f(int x,int y){ int r,nem=0; for(int l=1;l<=x;l=r+1){ r=min(x/(x/l),y/(y/l)); nem=(nem+(num[r]-num[l-1])%mod*getnum(1,x/l)%mod*getnum(1,y/l))%mod; } return nem%mod; } void calc(){ int r; for(int l=1;l<=n;l=r+1){ r=min(n/(n/l),m/(m/l)); ans=(ans+getnum(l,r)*f(n/l,m/l))%mod; } } signed main(){ sieve(); cin>>n>>m; if(n>m) swap(n,m); calc(); cout<<(ans%mod+mod)%mod; return 0; }

P4917 天守阁的地板P5221 Product

P4917 天守阁的地板

Product 凉心出题人又卡时间又卡空间

P3172 [CQOI2015] 选数

a1=LRa2=LR...an=LR[gcdi=1nai=k]

l=Lkr=Rk

a1=lra2=lr...an=lr[gcdi=1nai=1]

d=1rμ(d)a1=lr[d|a1]a2=lr[d|a2]...an=lr[d|an]

d=1rμ(d)(rdl1d)n

点击查看代码
#include<bits/stdc++.h> #define XD 114514 #define MAXN 10000010 #define int long long using namespace std; const int mod=1e9+7; int n,k,L,R,ans; int mu[MAXN],musum[MAXN];bool vis[MAXN]; vector<int> prm; map<int,int> mmu; void sieve(){ mu[1]=musum[1]=1; for(int i=2;i<=5e6;i++){ if(!vis[i]) prm.push_back(i),mu[i]=-1; musum[i]=musum[i-1]+mu[i]; for(auto j:prm){ if(i*j>5e6) break; vis[i*j]=true; if(i%j==0) break; mu[i*j]=-mu[i]; } } } int getmu(int x){ if(x<=5e6) return musum[x]; if(mmu.count(x)) return mmu[x]; int nem=1,r; for(int l=2;l<=x;l=r+1){ r=x/(x/l); nem-=getmu(x/l)*(r-l+1); } return mmu[x]=nem; } int power(int x,int y){ int nem=1; while(y){ if(y&1) nem=nem*x%mod; x=x*x%mod;y>>=1; } return nem; } void calc(){ int r;L--; for(int l=1;l<=R;l=r+1){ if(!(L/l)) r=R/(R/l); else r=min(R/(R/l),L/(L/l)); ans=(ans+power(R/l-L/l,n)*(getmu(r)-getmu(l-1))%mod)%mod; } } signed main(){ sieve(); cin>>n>>k>>L>>R; L=L/k+(L%k?1:0);R=R/k; calc(); cout<<(ans%mod+mod)%mod; return 0; }

P6055 [RC-02] GCD

i=1nj=1np=1njq=1nj[gcd(i,j)=1][gcd(p,q)=1]

i=1nj=1np=1jq=1j[gcd(i,j)=1][gcd(p,q)=j]

i=1np=1nq=1n[gcd(i,p,q)=1]

d=1nμ(d)nd3

点击查看代码
#include<bits/stdc++.h> #define XD 114514 #define MAXN 5000010 #define int long long using namespace std; const int mod=998244353; int n; int mu[MAXN],musum[MAXN];bool vis[MAXN]; vector<int> prm; map<int,int> mmu; void sieve(){ mu[1]=musum[1]=1; for(int i=2;i<=5e6;i++){ if(!vis[i]) prm.push_back(i),mu[i]=-1; musum[i]=musum[i-1]+mu[i]; for(auto j:prm){ if(i*j>5e6) break; vis[i*j]=true; if(i%j==0) break; mu[i*j]=-mu[i]; } } } int getmu(int x){ if(x<=5e6) return musum[x]; if(mmu.count(x)) return mmu[x]; int nem=1,r; for(int l=2;l<=x;l=r+1){ r=x/(x/l); nem-=(r-l+1)*getmu(x/l); } return mmu[x]=nem; } int ans; signed main(){ sieve(); cin>>n; int r; for(int l=1;l<=n;l=r+1){ r=n/(n/l); ans=(ans+(getmu(r)-getmu(l-1))%mod*(n/l)%mod*(n/l)%mod*(n/l)%mod)%mod; } cout<<(ans%mod+mod)%mod; return 0; }

P4449 于神之怒加强版

假设 n<m

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

i=1nj=1md=1ndk[gcd(i,j)=d]

d=1ndki=1ndj=1md[gcd(i,j)=1]

d=1ndkp=1ndμ(p)i=1nd[p|i]j=1md[p|j]

d=1ndkp=1ndμ(p)ndpmdp

T=dp

T=1nnTmTd|Tdkμ(Td)

不难发现 d|Tdkμ(Td) 是个积性函数,所以可以像求欧拉函数一样用线性筛求出。

具体来说,设 f(T)=d|Tdkμ(Td),则:

f(ab)={f(a)×f(b) gcd(a,b)=1f(a)×bk gcd(a,b)1,bP

然后知道 f(1)=1f(p)=pk1pP),所以就可以提前线性筛出来了。

点击查看代码
#include<bits/stdc++.h> #define XD 114514 #define MAXN 5000010 #define int long long using namespace std; const int mod=1e9+7; int t,n,m,k,ans; vector<int> prm;bool vis[MAXN]; int sum[MAXN],num[MAXN]; int power(int x,int y){ int nem=1; while(y){ if(y&1) nem=nem*x%mod; x=x*x%mod;y>>=1; } return nem; } void sieve(){ num[1]=sum[1]=1; for(int i=2;i<=5e6;i++){ if(!vis[i]){ prm.push_back(i); num[i]=power(i,k)-1; } sum[i]=(sum[i-1]+num[i])%mod; for(auto j:prm){ if(i*j>5e6) break; vis[i*j]=true; if(i%j==0){ num[i*j]=num[i]*(num[j]+1)%mod; break; } num[i*j]=num[i]*num[j]%mod; } } } void calc(){ int r;ans=0; for(int l=1;l<=n;l=r+1){ r=min(n/(n/l),m/(m/l)); ans=(ans+(sum[r]-sum[l-1])*(n/l)%mod*(m/l)%mod+mod)%mod; } } signed main(){ ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); cin>>t>>k; sieve(); while(t--){ cin>>n>>m; if(n>m) swap(n,m); calc(); cout<<(ans+mod)%mod<<"\n"; } return 0; }

学习建议

建议看以下的博客,讲的非常好,反正我的也看不懂。QWQ

浅谈莫反

狄利克雷卷积和莫比乌斯反演

(我本人最推荐这两个。QWQ)

莫比乌斯反演-让我们从基础开始(洛谷日报上的)

「笔记」积性函数

莫比乌斯反演

peng-ym 的杜教筛

算法学习笔记(35): 狄利克雷卷积


__EOF__

本文作者wdgm4
本文链接https://www.cnblogs.com/wdgm4/p/17704213.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   wdgm4  阅读(96)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示