以下内容中,a|b表示a是b的因数,a/|b表示a不是b的因数
前置知识:积性函数相关
整除分块
狄利克雷卷积
定义:任意函数f(n),g(n)的狄利克雷卷积为(f∗g)(n)=∑d|nf(d)g(nd)
其中∑d|n是要枚举n的因数并累加
换一种写法就是∑xy=nf(x)g(y)
还可以用代码写成:
int dirichlet(int n) {
int ans=0;
for(int i=1; i<=n; ++i) {
if(n%i==0) {
ans+=f[i]*g[n/i];
}
}
return ans;
}
这个卷积满足:
- 交换律f∗g=g∗f
- 结合律f∗g∗h=f∗(g∗h)
- 分配率f∗(g+h)=f∗g+f∗h
- 当f(x),g(x)都为积性函数时,(f∗g)(x)为积性函数
- 当(f∗g)(x),g(x)都为积性函数时,f(x)为积性函数
常见的狄利克雷卷积有:
- φ∗I=id
- μ∗I=ϵ
- ϵ∗f=f
- id∗I=σ
- I∗I=d
- (λ∗I)(n)=[√n∈Z]
简单证明:(3,4,5见相关函数的定义:积性函数相关)
- φ∗I=id(下面以n=6为例)
将[1,n]的整数都除以n:
1÷6=16,5÷6=56,对应与6互质的数,个数为φ(6)
2÷6=13,4÷6=23,对应与3互质的数,个数为φ(3)
3÷6=12,对应与2互质的数,个数为φ(2)
6÷6=11,对应与1互质的数,个数为φ(1)
即φ(1)+φ(2)+φ(3)+φ(6)=6(∑d|6φ(d)=6)
- μ∗I=ϵ
设n质因数分解后为k∏i=1pcii
当n>1时:
∑d|nμ(d)=μ(1)+μ(p1)+μ(p21)+⋯+μ(pcii)+⋯+μ(pc11pc22⋯pckk)=μ(1)+μ(p1)+μ(p21)+⋯+μ(pcii)+⋯+μ(pc11)μ(pc22)⋯μ(pckk)=k∏i=1ci∑j=0μ(pji)=k∏i=11∑j=0μ(pji)=k∏i=1(1−1)=0
当n=1时,∑d|nμ(d)=μ(1)=1
- (λ∗I)(n)=[√n∈Z]
同样设n质因数分解后为k∏i=1pcii
∑d|nλ(d)=λ(1)+λ(p1)+λ(p21)+⋯+λ(pcii)+⋯+λ(pc11pc22⋯pckk)=λ(1)+λ(p1)+λ(p21)+⋯+λ(pcii)+⋯+λ(pc11)λ(pc22)⋯λ(pckk)=k∏i=1ci∑j=0λ(pji)=k∏i=1ci∑j=0(−1)j=k∏i=1[ci≡0(mod2)]
最后那个式子只有在ci都为偶数的时候才为1,所以n为完全平方数时才是1。
一道题
给出n(n≤1012),求n∑k=1∑i|k∑j|iλ(i)λ(j)(mod998244353)
我们可以先枚举i,再枚举满足i|k的k:
n∑i=1∑i|k∑j|iλ(i)λ(j)
可以提出公因式:
n∑i=1λ(i)(∑i|k1)∑j|iλ(j)
中间的部分就是求小于等于n的i的倍数有多少,也就是⌊ni⌋
n∑i=1λ(i)⌊ni⌋∑j|iλ(j)
后面的部分是λ∗I,上面说了,是[√n∈Z]
n∑i=1λ(i)⌊ni⌋[√i∈Z]
这样我们就只用考虑完全平方数了。
而且当i为完全平方数时λ(i)=1
√n∑i=1⌊ni2⌋
O(√n)可以通过。
再来一题
P3455 [POI2007]ZAP-Queries
先把答案用式子表示一下:
a∑i=1b∑j=1[gcd(i,j)=d]
之后把i,j同时除以d:
⌊ad⌋∑i=1⌊bd⌋∑j=1[gcd(i,j)=1]
[gcd(i,j)=1]就是ϵ(gcd(i,j)),把ϵ=μ∗1代入上式得:
⌊ad⌋∑i=1⌊bd⌋∑j=1∑k|gcd(i,j)μ(k)
把最后一个循环提前:
⌊ad⌋∑k=1μ(k)⌊ad⌋∑i=1⌊bd⌋∑j=1[k|gcd(i,j)]
k|gcd(i,j)其实就是k|i&k|j,代入上式
⌊ad⌋∑k=1μ(k)⌊ad⌋∑i=1⌊bd⌋∑j=1[k|i&k|j]
=⌊ad⌋∑k=1μ(k)⌊adk⌋∑i=1⌊bdk⌋∑j=1
=⌊ad⌋∑k=1μ(k)⌊adk⌋⌊bdk⌋
线性筛μ的前缀和并用整除分块就可以达到每次询问O(√a),总复杂度O(T√a)
#include<bits/stdc++.h>
using namespace std;
const int MAXN=50005;
typedef long long ll;
bool isp[MAXN];
ll sum[MAXN];
int prime[MAXN],pcnt,mu[MAXN];
ll f(int n,int m) {
ll ans=0;
if(n>m)swap(n,m);
for(int l=1,r; l<=n; l=r+1) {
r=min(n/(n/l),m/(m/l));
ans+=(sum[r]-sum[l-1])*1ll*(n/l)*(m/l);
}
return ans;
}
int main() {
sum[1]=mu[1]=1;
for(int i=2; i<MAXN; ++i) {
if(!isp[i]) {
prime[++pcnt]=i;
mu[i]=-1;
}
for(int j=1,x; j<=pcnt&&(x=i*prime[j])<MAXN; ++j) {
isp[x]=true;
if(i%prime[j]) {
mu[x]=-mu[i];
} else {
mu[x]=0;
break;
}
}
sum[i]=sum[i-1]+mu[i];
}
int t,a,b,d;
scanf("%d",&t);
while(t--) {
scanf("%d%d%d",&a,&b,&d);
printf("%lld\n",f(a/d,b/d));
}
return 0;
}
莫比乌斯反演
第一种形式:对于f∗I=g,有f=g∗μ,反之亦然
证明:
两边都与μ做卷积
f∗I∗μ=g∗μ
结合律:
f∗(I∗μ)=g∗μ
由ϵ=1∗μ得:
f∗ϵ=g∗μ
由f∗ϵ=f得:
f=g∗μ
第二种形式:∑n|df(d)=g(n),则f(n)=∑n|dg(d)μ(dn)
证明:
∑n|dg(d)μ(dn)
将d除以n:
=+∞∑d=1g(dn)μ(d)
把g写成f
=+∞∑d=1μ(d)∑nd|tf(t)
枚举t,d即是tn的因数
=∑n|tf(t)∑d|tnμ(d)
后面的是μ∗I的形式,改写成ϵ
=∑n|tf(t)[tn=1]=f(n)
来几道题:
三倍经验的题:
SP3871 GCDEX - GCD Extreme,UVA11426 拿行李(极限版) GCD - Extreme (II),UVA11424 GCD - Extreme (I)
我们换个思路,先写成这个形式:
n∑i=1i−1∑j=1gcd(i,j)
根据P2303的结论n∑i=1gcd(i,n)=∑d|ndφ(nd)(上面有)把式子写成:
n∑i=1((i∑j=1gcd(i,j))−gcd(i,i))=n∑i=1(∑d|idφ(id))−i=(n∑i=1∑d|idφ(id))−n(n+1)2
设g(i)=∑d|idφ(id)
我们可以试着O(n)线性筛出g(i),同时算个前缀和就预处理完了。
下面写的i,primej为欧拉筛代码中的i,prime[j]
我们设i分解质因数后是k∏j=1pcjj,lowi=pc11。其中p1是最小的质因子。
而且primej为i×primej得最小质因子(欧拉筛的性质)
则当primej|i时,p1=primej
可以得到,g(1)=1,g(p)=2p−1,p是质数。
由于函数id(x)和φ(x)是积性函数
那么g(i)=id∗φ也是积性函数
则当i与primej互质时,g(i×primej)=g(i)×g(primej),且lowi×primej=primej
当i与primej不互质即primej|i时,有p1=primej(上面讲了),根据low的定义得
gcd(ilowi,p1×lowi)=1,lowi×primej=lowi×primej
这样就可以用积性函数的定义求g(i×primej)
g(i×primej)=g(ilowi)×g(p1×lowi)
但是当i=lowi时,这个式子就会变成g(i×primej)=g(1)×g(i×primej)=g(i×primej),无法递推
特殊处理一下i=lowi的情况:
i=lowi即i=pc11,则i×primej=pc1+11
g(pc1+11)=∑d|pc1+11dφ(pc1+11d)
把d=1的一项单独提出:
g(pc1+11)=∑d|pc1+11&d>1dφ(pc1+11d)+φ(pc1+11)
∑内的d此时只能是p1,p21,⋯,pc1+11,我们就可以把d除以p1
g(pc1+11)=∑d|pc11dp1φ(pc1+11dp1)+φ(pc1+11)
化简一下,并且提出p1
g(pc1+11)=p1∑d|pc11dφ(pc11d)+φ(pc1+11)
可以发现∑d|pc11dφ(pc11d)=g(pc11),代入
g(pc1+11)=p1g(pc11)+φ(pc1+11)
也就是g(i×primej)=primej×g(i)+φ(i×primej)
总结一下g(x)=⎧⎪
⎪
⎪
⎪
⎪
⎪
⎪
⎪⎨⎪
⎪
⎪
⎪
⎪
⎪
⎪
⎪⎩1x=12x−1x∈primeg(i)×g(primej)x=i×primej,gcd(primej,i)=1primej×g(i)+φ(i×primej)x=i×primej,primej|i,lowi=ig(i×primej)=g(ilowi)×g(p1×lowi)x=i×primej,primej|i,lowi≠i
lowx=⎧⎪
⎪
⎪⎨⎪
⎪
⎪⎩0i=1xx∈primeprimejx=i×primej,gcd(i,primej)=1lowi×primejx=i×primej,primej|i
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN=4e6+100;
int prime[MAXN],pcnt,phi[MAXN+1],n;
ll g[MAXN+1],low[MAXN+1],sum[MAXN+1];
bool isp[MAXN+1];
int main() {
sum[1]=g[1]=phi[1]=1;
for(int i=2; i<=MAXN; ++i) {
if(!isp[i]) {
prime[++pcnt]=i;
phi[i]=i-1;
low[i]=i;
g[i]=(i<<1)-1;
}
for(int j=1,x; j<=pcnt&&(x=i*prime[j])<=MAXN; ++j) {
isp[x]=true;
if(i%prime[j]) {
g[x]=g[i]*g[prime[j]];
phi[x]=phi[i]*phi[prime[j]];
low[x]=prime[j];
} else {
low[x]=low[i]*prime[j];
phi[x]=phi[i]*prime[j];
g[x]=(low[i]==i)?g[i]*prime[j]+phi[x]:g[i/low[i]]*g[low[i]*prime[j]];
}
}
sum[i]=sum[i-1]+g[i];
}
while(~scanf("%d",&n)&&n) {
printf("%lld\n",sum[n]-((n+1ll)*n>>1));
}
return 0;
}
P2257 YY的GCD
这题是要求:∑p∈primen∑i=1m∑j=1[gcd(i,j)=p]
把i,j除以p
∑p∈prime⌊np⌋∑i=1⌊mp⌋∑j=1[gcd(i,j)=1]
可以对后面的部分进行莫比乌斯反演
设f(x)表示gcd(i,j)为x的二元组的个数,g(x)表示gcd(i,j)为x的倍数的二元组的个数,
把它俩用式子写就是:
f(x)=⌊np⌋∑i=1⌊mp⌋∑j=1[gcd(i,j)=x]
g(x)=⌊np⌋∑i=1⌊mp⌋∑j=1[x|gcd(i,j)]=⌊np⌋∑i=1⌊mp⌋∑j=1∑x|d[gcd(i,j)=d]
后面的∑x|d可以提前
g(x)=∑x|d⌊np⌋∑i=1⌊mp⌋∑j=1[gcd(i,j)=d]=∑x|df(x)
根据莫比乌斯反演第二形式,可得:f(x)=∑x|dg(d)μ(dx)
f(1)=∑1|dg(d)μ(d1)=⌊np⌋∑i=1g(i)μ(i)
再看g(x)=⌊np⌋∑i=1⌊mp⌋∑j=1[x|gcd(i,j)]这个式子,把i,j除以x可以得到
g(x)=⌊npx⌋∑i=1⌊mpx⌋∑j=1[1|gcd(i,j)]=⌊npx⌋∑i=1⌊mpx⌋∑j=11=⌊npx⌋⌊mpx⌋
代回f(1)=⌊np⌋∑i=1g(i)μ(i)=⌊np⌋∑i=1⌊npx⌋⌊mpx⌋μ(i)
提出⌊npx⌋⌊mpx⌋
f(1)=⌊npx⌋⌊mpx⌋⌊np⌋∑i=1μ(i)
再代回原来的式子:
∑p∈prime⌊npx⌋⌊mpx⌋⌊np⌋∑i=1μ(i)
先用T枚举px,再枚举p|T
T∑i=1⌊nT⌋⌊mT⌋∑p|T&p∈primeμ(i)
由于10000128以内的素数只有664583个,所以我们暴力计算后面的部分都不会超时。
前面的部分就可以用整除分块优化
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN=10000129;
int prime[MAXN>>3],pcnt;
bool vis[MAXN];
int mu[MAXN];
ll sum[MAXN],g[MAXN];
int read() {
int x=0,c=getchar();
bool f=1;
while(c<=47||c>=58) {
f=f&&(c^45);
c=getchar();
}
while(c>=48&&c<=57) {
x=(x<<3)+(x<<1)+(c&15);
c=getchar();
}
return f?x:-x;
}
int main() {
vis[0]=vis[1]=1;
mu[1]=1;
for(int i=2; i<MAXN; ++i) {
if(!vis[i]) {
prime[++pcnt]=i;
mu[i]=-1;
}
for(int j=1; i*1ll*prime[j]<MAXN&&j<=pcnt; ++j) {
vis[i*prime[j]]=1;
if(i%prime[j]==0)break;
else mu[i*prime[j]]=-mu[i];
}
}
for(int i=1; i<=pcnt; ++i) {
for(int j=1; j*1ll*prime[i]<MAXN; ++j) {
g[j*prime[i]]+=mu[j];
}
}
for(int i=1; i<MAXN; ++i) {
sum[i]=sum[i-1]+g[i];
}
int t=read(),n,m,l,r;
ll ans;
while(t--) {
n=read(),m=read();
if(n>m)swap(n,m);
ans=0;
for(l=1; l<=n; l=r+1) {
r=min(n/(n/l),m/(m/l));
ans+=(n/l)*(sum[r]-sum[l-1]+0ll)*(m/l);
}
printf("%lld\n",ans);
}
return 0;
}
P1829 [国家集训队]Crash的数字表格 / JZPTAB
这题是要求:n∑i=1m∑j=1lcm(i,j)
根据lcm的性质:
n∑i=1m∑j=1ijgcd(i,j)
加入一个d=gcd(i,j):
n∑d=1n∑i=1m∑j=1[gcd(i,j)=d]ijd
把i,j都除以d:
n∑d=1⌊nd⌋∑i=1⌊md⌋∑j=1[gcd(i,j)=1]ijd
提出d,i,把ϵ=I∗μ代入:
n∑d=1d⌊nd⌋∑i=1i⌊md⌋∑j=1j∑k|gcd(i,j)μ(k)
k|gcd(i,j)其实就是k|i&k|j(手玩一下可以知道)
n∑d=1d⌊nd⌋∑i=1i⌊md⌋∑j=1j∑k|i&k|jμ(k)
直接把k提前:
n∑d=1d⌊nd⌋∑k=1μ(k)∑k|i&i<⌊nd⌋i∑k|j&j<⌊md⌋j
把i,j都除以k
n∑d=1d⌊nd⌋∑k=1μ(k)⌊ndk⌋∑i=1ik⌊mdk⌋∑j=1jk
把两个k都提出来,把d乘进去,再设S(x)=1+2+⋯+x=x(x+1)2:
n∑d=1⌊nd⌋∑k=1dk2μ(k)S(⌊ndk⌋)S(⌊mdk⌋)
先枚举T=dk,再枚举k|T
n∑T=1∑k|TTkμ(k)S(⌊nT⌋)S(⌊mT⌋)
再提出TS(⌊nT⌋)S(⌊mT⌋)
n∑T=1TS(⌊nT⌋)S(⌊mT⌋)∑k|Tkμ(k)
我们设f(T)=∑k|Tkμ(k),看一下它的性质:
首先id,μ都是积性函数,那么id×μ也是积性函数,而且I也是积性函数,那么f=(id×μ)∗I也是积性函数。
那么欧拉筛这个f还需要看prime[j]|T的情况:
我们设T分解质因数后为pk11pk22⋯pkcc(不妨设其中p1=prime[j])则
f(Tp1)−f(T)=∑k|Tp1kμ(k)−∑k|Tkμ(k)=∑k|Tp1&k/|Tkμ(k)
Tp1=pk1+11pk22⋯pkcc,那么满足k|Tp1&k/|T的数必须是pk1+11的倍数,而且k1≥1即k1+1≥2那么μ(k)=0,那么后面那部分就是0,我们就得到:
f(Tp1)=f(T)
最后总结一下:f(Tp1)={f(T)p1|Tf(T)f(p1)p1/|T
最后要求n∑T=1TS(⌊nT⌋)S(⌊mT⌋)f(T),一边线性筛一边求就行了:
#include<bits/stdc++.h>
using namespace std;
const int mod=20101009;
const int MAXN=1e7+128;
typedef long long ll;
int n,m,ans;
bool isp[MAXN+1];
int prime[MAXN+1],f[MAXN+1],pcnt;
inline int S(int x) {
return (x*1ll*(x+1)/2)%mod;
}
int main() {
cin>>n>>m;
if(n>m)swap(n,m);
f[1]=1,isp[1]=true;
ans=(ans+S(n)*1ll*S(m))%mod;
for(int i=2; i<=n; ++i) {
if(!isp[i]) {
prime[++pcnt]=i;
f[i]=mod-i+1;
}
for(int j=1,x; j<=pcnt&&(x=i*prime[j])<=MAXN; ++j) {
isp[x]=true;
if(i%prime[j]) {
f[x]=f[i]*1ll*f[prime[j]]%mod;
} else {
f[x]=f[i];
break;
}
}
ans=(ans+S(n/i)*1ll*S(m/i)%mod*i%mod*f[i])%mod;
}
printf("%d\n",ans);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】