最近在准备关于莫比乌斯反演的课件,准备把例题简要整理下来,那就开始吧
求2+∑x=1n−1∑y=1n−1[gcd(x,y)==1](n≤40000)
最简单的做法,线性筛预处理出欧拉函数的前缀和,时间复杂度O(n)。
求∑p∑x=1n∑y=1n[gcd(x,y)==p](n≤107)
其实就是∑p∑x=1⌊pn⌋∑y=1⌊pn⌋[gcd(x,y)==1],枚举所有质数后就是和上一道题一样做法了,双倍经验。
求∑i=1ngcd(i,n),n≤232
变形一下,枚举每种gcd的值,即
==i=1∑ngcd(i,n)d∣n∑dx=1∑dn[gcd(x,dn)==1]d∣n∑dφ(dn)
那么根号枚举约数,单点求欧拉函数就做完了。
时间复杂度?是个经典的东西O(∑i=1ni+in)=O(n43)
求第k(k≤109)个不含完全平方数因子的正整数
二分答案,转为求前n个数中有多少数不含完全平方因子。容斥一下,总数减去是4(2的平方)的倍数的,减去9的倍数,16的倍数不用管,前面减过了,再减去25的倍数,加上36的倍数,因为前面多减了一次…那这样就发现了容斥系数就是莫比乌斯函数。时间复杂度O(nlogn)
求∑p∑x=1n∑y=1m[gcd(x,y)==p](n≤107),T(≤10000)组询问
bzoj 2818的加强版,把其中一个n换成了m,这样就不能用欧拉函数了,推导一下:
下述的p表示质数
======p∑x=1∑ny=1∑m[gcd(x,y)==p]p∑x=1∑⌊pn⌋y=1∑⌊pm⌋[gcd(x,y)==1]p∑x=1∑⌊pn⌋y=1∑⌊pm⌋d∣gcd(x,y)∑μ(d)p∑x=1∑⌊pn⌋y=1∑⌊pm⌋d∣x∧d∣y∑μ(d)p∑d=1∑⌊pmin(n,m)⌋μ(d)⌊pdn⌋⌊pdm⌋k=1∑min(n,m)p,p∣k∑μ(pk)⌊kn⌋⌊km⌋k=1∑min(n,m)f(k)⌊kn⌋⌊km⌋
其中第三行莫比乌斯反演,第五行交换求和,第六行令k=pd,转而枚举k,化简到最后用筛法预处理出所有f的值即可,这里应该可以用线性筛,不过懒得推导的话也可以枚举所有质数再枚举它的倍数这样求,那么时间复杂度就和埃氏筛一样了,之后每次求一组询问就是经典的数论分块了。时间复杂度O(nloglogn+Tn)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| #include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=10000000; bool vis[N+5]; int prime[N+5],cnt; int mu[N+5],f[N+5]; void getprime() { mu[1]=1; for(int i=2;i<=N;i++) { if(!vis[i]) { prime[cnt++]=i; mu[i]=-1; } for(int j=0;j<cnt && prime[j]<=N/i;j++) { vis[i*prime[j]]=true; if(i%prime[j]) mu[i*prime[j]]=-mu[i]; else { mu[i*prime[j]]=0; break; } } } for(int i=0;i<cnt;++i) { int x=prime[i]; for(int j=x;j<=N;j+=x) f[j]+=mu[j/x]; } for(int i=1;i<=N;++i) f[i]+=f[i-1]; } ll calc(int n,int m) { if(n>m) swap(n,m); ll ans=0; for(int i=1,j;i<=n;i=j+1) { j=min(n/(n/i),m/(m/i)); ans+=1LL*(f[j]-f[i-1])*(n/i)*(m/i); } return ans; } int main() { getprime(); int T; scanf("%d",&T); while(T--) { int n,m; scanf("%d%d",&n,&m); printf("%lld\n",calc(n,m)); } }
|
求∑i=1nlcm(i,n),n≤106,T(≤300000)组询问。
======i=1∑nlcm(i,n)i=1∑ngcd(i,n)i×n21i=1∑n−1gcd(i,n)n2+n21d∣n,d=n∑dn2k=1∑⌊dn⌋[gcd(k,dn)==1]+n21nd∣n,d=n∑dnφ(dn)+n21nd∣n,d=1∑dφ(d)+n21nf(n)+n
这样线性筛预处理完,O(nlogn)的求出函数值f,每次回答O(1)即可。
求∑i=1n∑j=1mlcm(i,j),n,m≤107
=======i=1∑nj=1∑mlcm(i,j)i=1∑nj=1∑mgcd(i,j)ijd=1∑min(n,m)x=1∑⌊dn⌋y=1∑⌊dm⌋dxy[gcd(x,y)==1]d=1∑min(n,m)dx=1∑⌊dn⌋y=1∑⌊dm⌋xyk∣x∧k∣y∑μ(k)d=1∑min(n,m)dk=1∑⌊dm⌋μ(k)x=1∑⌊dkn⌋y=1∑⌊dkm⌋k2xyT=1∑min(n,m)k∣T∑kTμ(k)g(⌊Tn⌋,⌊Tm⌋)T=1∑min(n,m)Tg(⌊Tn⌋,⌊Tm⌋)k∣T∑kμ(k)T=1∑min(n,m)Tg(⌊Tn⌋,⌊Tm⌋)f(T)
其中g(n,m)=∑i=1n∑j=1mij=2n(n+1)2m(m+1)
一开始头铁,O(nlogn)求的f(T)的值,超时无疑,然后学习了用线性筛求积性函数f(T)=∑k∣Tkμ(k)的思路。
线性筛过程分两种情况讨论
1.primej整除i,那么枚举i×primej的约数k时,如果关于primej的指数大于等于2的话莫比乌斯函数为零,对答案没有贡献,所以此时:
f(i×primej)=f(i)
2.primej不整除i,那么再分枚举的约数k是否含有primej,不含有的部分就是f(i),含primej的话,整体乘上这么多,并且莫比乌斯函数要变号,是−f(i)primej。综上,此时:
f(i×primej)=(1−primej)f(i)
这样这道题就做完了,时间复杂度O(n)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| #include<bits/stdc++.h> using namespace std; typedef long long ll; const int mod=20101009; const int N=10000000; bool vis[N+5]; int prime[666666],cnt; int f[N+5]; void getprime() { f[1]=1; for(int i=2;i<=N;i++) { if(!vis[i]) { prime[cnt++]=i; f[i]=(1-i+mod); } for(int j=0;j<cnt && i*prime[j]<=N;j++) { vis[i*prime[j]]=true; if(i%prime[j]) { f[i*prime[j]]=1LL*(1-prime[j]+mod)*f[i]%mod; } else { f[i*prime[j]]=f[i]; break; } } }
} int Sum(int n) { return 1LL*(n+1)*n/2%mod; } int main() { getprime(); int n,m; scanf("%d%d",&n,&m); if(n<m) swap(n,m); ll ans=0; for(int i=1;i<=m;++i){ ans+=1LL*i*Sum(n/i)%mod*Sum(m/i)%mod*f[i]%mod; if(ans>=mod) ans-=mod; } printf("%lld\n",ans); }
|
求∑i=1n∑j=1md(ij),n,m≤50000,T(≤50000)组询问,其中d是约数个数函数。
这个题一开始卡在一个结论上了,首先要知道d(ij)=∑x∣i∑y∣j[gcd(x,y)==1],这个结论不好想,但可以用分解质因数的形式结合约数个数定理推出来,那解决了第一步,后面的就简单了。
======i=1∑nj=1∑md(ij)i=1∑nj=1∑mx∣i∑y∣j∑[gcd(x,y)==1]i=1∑nj=1∑mx∣i∑y∣j∑k∣gcd(x,y)∑μ(k)i=1∑nj=1∑mx∣i∑y∣j∑k∣x∧k∣y∑μ(k)k=1∑min(n,m)μ(k)x=1∑⌊kn⌋y=1∑⌊km⌋⌊xkn⌋⌊ykm⌋k=1∑min(n,m)μ(k)x=1∑⌊kn⌋⌊xkn⌋f(⌊km⌋)k=1∑min(n,m)μ(k)f(⌊kn⌋)f(⌊km⌋)
至此又可以数论分块搞定单组询问了,时间复杂度O(Tn)。
化简下题意就是求∑i=1n∑j=1mlcm(i,j)[μ2(gcd(i,j))==1],n,m≤4000000,对230取模,T(≤2000)组询问。发现就是在第七题的基础上加了个多组询问,且只统计μ2(gcd(i,j))==1的。
那么前面的推导都是一样的,最后化简到
原式=∑T=1min(n,m)g(⌊Tn⌋,⌊Tm⌋)T∑k∣Tkμ(k)μ2(kT)
头铁依然会TLE,还是先观察一下
f(T)=∑k∣Tkμ(k)μ2(kT)是积性函数
且当T分解质因数后的指数中有大于2的情况的话,显然此时f(T)=0,并且易知f(p)=1−p,f(p2)=−p,这样又可以线性筛搞一搞了,然后预处理出if(i)的前缀和。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| #include<bits/stdc++.h> using namespace std; typedef long long ll; const int mod=1<<30; const int N=4000000; bool vis[N+5]; int prime[N+5],cnt; unsigned int f[N+5],sum[N+5]; void getprime() { f[1]=1; for(int i=2;i<=N;i++) { if(!vis[i]) { prime[cnt++]=i; f[i]=1U-i; } for(int j=0;j<cnt && i*prime[j]<=N;j++) { vis[i*prime[j]]=true; if(i%prime[j]) { f[i*prime[j]]=f[i]*f[prime[j]]; } else { if(i==prime[j]) f[i*prime[j]]=-prime[j]; else if(i/prime[j]%prime[j]==0) f[i*prime[j]]=0; else f[i*prime[j]]=f[i/prime[j]]*f[prime[j]*prime[j]]; break; } } }
for(int i=1;i<=N;++i) sum[i]=sum[i-1]+1U*i*f[i]; } unsigned int Sum(int n) { return 1LL*(n+1)*n/2; } int main() { getprime(); int T; scanf("%d",&T); while(T--){ int n,m; scanf("%d%d",&n,&m); if(n<m) swap(n,m); unsigned int ans=0; for(int i=1,j;i<=m;i=j+1){ j=min(n/(n/i),m/(m/i)); ans+=1U*(sum[j]-sum[i-1])*Sum(n/i)*Sum(m/i); } printf("%d\n",(int)(ans%mod)); } }
|
求∏i=1n∏j=1mf(gcd(i,j)),n,m≤106,其中f为斐波那契数列,结果模109+7,T(≤1000)组询问。
==i=1∏nj=1∏mf(gcd(i,j))d=1∏min(n,m)f(d)∑i=1⌊dn⌋∑i=1⌊dm⌋[gcd(i,j)==1]d=1∏min(n,m)f(d)∑k=1min(⌊dn⌋,⌊dm⌋)μ(k)⌊dkn⌋⌊dkm⌋
这样指数部分是关于⌊dn⌋,⌊dm⌋的函数,可以分块,又因为外层是枚举的d,所以分块套分块即可!时间复杂度O(T(n43+nlogn)),前一部分来源分析同第三题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| #include<bits/stdc++.h> using namespace std; typedef long long ll; const int mod=1e9+7; #define N 1000000 bool vis[N+5]; int prime[N+5],cnt; int mu[N+5],sum[N+5]; int f[N+5],pre[N+5]; void getprime() { mu[1]=1; for(int i=2;i<=N;i++) { if(!vis[i]) { prime[cnt++]=i; mu[i]=-1; } for(int j=0;j<cnt && prime[j]<=N/i;j++) { vis[i*prime[j]]=true; if(i%prime[j]) mu[i*prime[j]]=-mu[i]; else { mu[i*prime[j]]=0; break; } } } for(int i=1;i<=N;++i) sum[i]=sum[i-1]+mu[i]; } int power(int x,ll n){ int ans=1; while(n){ if(n&1) ans=1LL*ans*x%mod; x=1LL*x*x%mod; n>>=1; } return ans; } ll calc(int n,int m,int d){ ll ans=0; for(int i=1,j;i<=n;i=j+1){ j=min(n/(n/i),m/(m/i)); ans+=1LL*(sum[j]-sum[i-1])*(n/i)*(m/i); } return ans; } int main(){ getprime();
pre[0]=pre[1]=f[1]=1; for(int i=2;i<=N;++i){ f[i]=f[i-1]+f[i-2]; if(f[i]>=mod) f[i]-=mod; pre[i]=1LL*pre[i-1]*f[i]%mod; } int T; scanf("%d",&T); while(T--){ int n,m; scanf("%d%d",&n,&m); if(n>m) swap(n,m); int ans=1; for(int i=1,j;i<=n;i=j+1){ j=min(n/(n/i),m/(m/i)); int x=1LL*pre[j]*power(pre[i-1],mod-2)%mod; ll y=calc(n/i,m/i,i); ans=1LL*ans*power(x,y)%mod; } printf("%d\n",ans); } }
|