反演与狄利克雷卷积

修订日期:2021.12.3. 更改了很多本质上的错误。

建议首先阅读组合数学相关 Part 3. 容斥原理

1. 本质与第一反演公式

1.1. 定义

反演是 f 表示 g g 表示 f

已知 g(n)=i=1ncn,if(i),根据系数 ci,j 可以反推出用 g 表示 f 的方法:f(n)=i=1ndn,ig(i)。这样,就算 f 无法直接求,也可以通过所有 g(i) 推出 f(n)。上述二式互为反演公式。

1.2. 第一反演公式

直接给出定理:如果存在 n 次多项式 p,q 分别满足以下公式:[xn]p=i=1ncn,i×[xi]q 以及 [xn]q=i=1ndn,i×[xi]p,且 ci,idi,i0,那么对于任意序列 u,v,都有:

vn=i=1ncn,iuiun=i=1ndn,ivi

如果将 c,d 写成矩阵 C,D,那么它是下三角矩阵。将 p,q 写成列向量 p,q,那么条件中的两个公式可以表示为 p=Cqq=Dp,即 p=CDpCD=I。由于 CD 互逆,所以它们的行列式不为 0,而因为它们是下三角矩阵,因此一定有对角线元素不为 0,这解释了为什么 ci,idi,i0

只要我们找到任何一组符合条件的 p,qc,d,就可以将 p,q 推广至任意 n 次多项式,因为 CD 恒互逆,不随 p,q 的改变而改变。这说明为了证明某个反演公式成立,我们只需考虑一种方便的特殊非平凡情况即可

[c1,1000c2,1c2,200cn1,1cn1,2cn1,n10cn,1cn,2cn,n1cn,n][q1q2qn1qn]=[p1p2pn1pn]

[c1,1000c2,1c2,200cn1,1cn1,2cn1,n10cn,1cn,2cn,n1cn,n][d1,1000d2,1d2,200dn1,1dn1,2dn1,n10dn,1dn,2dn,n1dn,n]=I

2. 二项式反演

2.1. 引入

在把玩二项式定理的时候,尝试把 x 变成 1+(x1) 可能会有新的收获:

xn=(1+x1)n=i=0n(ni)1ni(x1)i=i=0n(ni)(x1)i

嗯?和反演公式的形式长得很像!那么试试能不能用 x 表示 x1

(x1)n=i=0n(ni)(1)nixi

很好,令 pi=xiqi=(x1)ici,j=(ij)di,j=(ij)(1)ij,那么我们就成功找到了一组反演公式,称为形式一。

fn=i=0n(ni)gign=i=0n(1)ni(ni)fi

代数化证明:

fn=i=0n(ni)j=0i(1)ij(ij)fj=j=0nfji=jn(1)ij(ni)(ij)=j=0nfj(nj)i=jn(1)ij(njij)=j=0nfj(nj)t=0nj(1)t(njt)

众所周知,右边的 当且仅当 nj=0j=n 时为 1,否则为 0。因此 fn=j=0nfn(nj)[n=j]=fn,证毕。

[(00)000(10)(11)00(n10)(n11)(n1n1)0(n0)(n1)(nn1)(nn)][(00)000(10)(11)00(1)n1(n10)(1)n2(n11)(n1n1)0(1)n(n0)(1)n1(n1)(nn1)(nn)]=I

如果将矩阵转置,那么还可以得到另一种更常用表示,形式二:

fi=j=in(ji)gjgi=j=in(1)ji(ji)fj

[(00)(10)(n10)(n0)0(11)(n11)(n1)00(n1n1)(nn1)000(nn)][(00)(10)(1)n1(n10)(1)n(n0)0(11)(1)n2(n11)(1)n1(n1)00(n1n1)(nn1)000(nn)]=I

2.2. 组合意义

二项式反演的本质是容斥原理fi 表示钦定选 i的方案数,gi 表示恰好选 i的方案数。对于任意的 ingifn 中被计算了 (in) 次,故 fn=i=nm(in)gi ,其中 m 是数目上界,恰好对应形式二。这说明可以使用 i=nm(1)in(in)fi 求出 gn

注意:在定义中,fn 表示先钦定 n 个,再统计钦定情况如此的方案数,其中会包含重复的方案,因为一个方案可以有多种钦定情况。具体地,对于恰好选择 i 个的方案,钦定情况数为 (in) ,故 gifn 中被计算了 (ni) 次。切勿将 fn 理解为普通的 gi 的后缀和。

很多初学者最大的误区在于将 fn 理解为至少选 n的方案数,这是完全错误的(如果真是这样那么 gn 不就等于 fnfn+1 么,还要二项式反演干啥 = . =)。

2.3. 应用

OI 界二项式反演的应用常结合动态规划:DP 求出钦定选择 i 个元素时的总方案,然后就可以通过二项式反演得到恰好选择 i 个元素时的方案数。综上所述,我们总结出二项式定理的核心:通过二项式系数的容斥进行 “钦定” 和 “恰好” 的转换

2.3.1. 错排问题

n 个人排列,求每个人都恰好站错的方案数。即求 p[i, pii]

gn 表示 n 个人的错排方案数,而 fn 表示所有排列方案数即 n!。那么有 fn=i=0n(ni)gii 的意义是枚举站错的人的个数。反演得到 gn=i=0n(1)ni(ni)fi=i=0n(1)nin!(ni)!(组合数分母上的 i!fi 抵消了)。

2.3.2. 染色问题 I(一维)

n 个格子,k 种颜色,相邻格子颜色不同,每个颜色至少出现一次,求方案数。

gk 表示恰好出现 k 个颜色的方案数,fk 表示 k 个颜色的总方案数,即 k×(k1)n1。有

fk=i=0k(ki)gi

反演得到 gk=i=0k(1)ki(ki)fi。可以 O(klogn) 计算。

2.3.3. 染色问题 II(二维)

n×m 个格子,1 种颜色,每行和每列至少有一个格子被涂色,求方案数。

gi,j 表示恰好有 ij 列被涂色的方案数,fi,j 表示 ij 列被涂色的方案数即 2ij,有

fn,m=i=0n(ni)j=0m(mj)gi,j

ij 分别二项式反演,有

gn,m=i=0nj=0m(1)(ni)+(mj)(ni)(mj)fi,j

2.3.4. 染色问题 III(三维)

n×m 个格子,k 种颜色,每行和每列至少一个格子被涂色,每个颜色至少出现一次,格子可不被涂色,求方案数。

请读者结合染色问题 I. 与 II. 自行思考。设 gn,m,k 表示恰有 nm 列至少有一个格子被涂上颜色,且恰有 k 种颜色的方案数,fn,m,k 表示总方案数 (k+1)nm

fn,m,k=i=0nj=0mp=0k(ni)(mj)(kp)gi,j,k

gn,m,k=i=0nj=0mp=0k(1)n+m+kijp(ni)(mj)(kp)(p+1)ij

直接计算 n3,可以通过二项式定理化简减小复杂度。

2.4. 参考文章

2.5. 例题

*I. P6478 [NOI Online #2 提高组] 游戏

很不错的题目,可以用来入门二项式反演。设 fx钦定 x 对祖先关系的情况总数,gx 为答案,那么根据二项式反演有 gx=i=xn(1)ix(ix)fifx 可以树形背包求,别忘了最后乘上 (mx)!,因为剩下 mx 对点对顺序任意。时间复杂度 O(n2)

*II. P4859 已经没有什么好害怕的了

和例题 I. 极度类似。DP 方法几乎一样,最后也都要乘以阶乘作为系数。实际上,上一题就是把本题搬到了树上。

const int mod = 1e9 + 9;
const int N = 2e3 + 5;
int n, k, ans, a[N], b[N], num[N], f[N], fc[N], ifc[N];
void add(int &x, int y) {x += y, x >= mod && (x -= mod);}
int ksm(int a, int b) {
	int s = 1;
	while(b) {
		if(b & 1) s = 1ll * s * a % mod;
		a = 1ll * a * a % mod, b >>= 1;
	} return s;
}
int main(){
	cin >> n >> k;
	if((n & 1) != (k & 1)) return puts("0"), 0;
	k = n + k >> 1, fc[0] = 1;
	for(int i = 1; i <= n; i++) fc[i] = 1ll * fc[i - 1] * i % mod;
	ifc[n] = ksm(fc[n], mod - 2);
	for(int i = n - 1; ~i; i--) ifc[i] = 1ll * ifc[i + 1] * (i + 1) % mod;
	for(int i = 1; i <= n; i++) cin >> a[i];
	for(int i = 1; i <= n; i++) cin >> b[i];
	sort(a + 1, a + n + 1), sort(b + 1, b + n + 1);
	for(int i = 1; i <= n; i++) {
		num[i] = num[i - 1];
		while(num[i] < n && a[i] > b[num[i] + 1]) num[i]++;
	} f[0] = 1;
	for(int i = 1; i <= n; i++)
		for(int j = min(num[i], i); j; j--)
			add(f[j], 1ll * f[j - 1] * (num[i] - j + 1) % mod);
	for(int i = k; i <= n; i++) {
		int bin = 1ll * fc[i] * ifc[k] % mod * ifc[i - k] % mod;
		int coef = 1ll * bin * f[i] % mod * fc[n - i] % mod;
		add(ans, (i - k) & 1 ? mod - coef : coef);
	} cout << ans << endl;
	return 0;
}

*III. CF1228E Another Filling the Grid

gi,j 表示恰有 ij 列的最小值为 1。若钦定最小值为 1 无法计算,但钦定最小值不为 1 就很好算了。因此我们设 fi,j 表示钦定 ij 列使得其它行列的最小值非 1 的方案数,即 (k1)(ni)(nj)×kn2(ni)(nj),那么有

fn,n=i=0nj=0n(ni)(nj)gi,j

二项式反演得到

gn,n=i=0nj=0n(1)ni+nj(ni)(nj)fi,j

可以 O(n2logk) 计算,时间复杂度足够优秀。当然,精细实现可以做到平方甚至线性对数。

IV. [BZOJ2839]集合计数

考虑钦定 i 个元素在交集里面,那么方案数为 (ni)22ni。二项式反演掉,得到答案为 i=kn(1)ki(ik)(ni)22ni

V. [BZOJ4665]小 w 的喜糖

钦定有 k 组不同的方案数不好算,考虑钦定 k 组相同的方案数为 fk,恰好 k 组相同的方案数为 gk,有

fi=j=in(ij)gj

二项式反演即可,fk 可以通过简单背包乘以二项式系数计算(fi=j=0cfij(cj)cj)。注意最后要除掉 (cnti)!,因为种类相同的糖本质相同。时间复杂度平方。

*VI. [BZOJ2162]男生女生

本题可以看做割裂的两部分,一部分是求使某部点尽量大的二分图最大团,见 网络流与二分图 Part 2.5. 二分图的最大团。另一部分则需要用到二项式反演:

考虑求出男生个数 B 和女生个数 G 后如何计算答案:钦定 i 个男生和 j 个女生不能被选,方案数为 fi,j=((Bi)(Gj)k)。设恰好 ij 女没有被选到的方案数为 gi,j,那么 fi,j=i=iBj=jG(ii)(jj)gi,j,二项式反演掉就做完了。由于模数不是质数所以预处理组合数的复杂度是瓶颈,为 O(n4)

3. 单位根反演

3.1. 公式

[nx]=1ni=0n1ωnxi

常规证法是用等比数列求和公式,我不太喜欢,因为这样不能使初学单位根反演的同学感受到它的自然与优美。

一个比较直观的我自己的理解是 ωnxnx 时等于 1n 个求和后再除以 n 就是 1;而在 nx 时不妨设 d=gcd(x,n)。把单位根放到平面直角坐标系里感受一下,i=0n1(ωnx)i 就是把 ωnx 旋转 n 次,每次旋转相同的 2πxn 角度。那么最终形成了相同的 d 组 “向量”,每组内部有 nd 个在单位圆上均匀分布的 “向量”,根据高中知识可知每一组向量的和都是 0

它还有另外一种形式,在做题时经常会遇到

[xy(modn)]=[xy0(modn)]=1ni=0n1ωn(xy)i

3.2. 应用

我们为什么要用单位根反演?钻研这个问题,不仅可以帮助我们深入理解单位根反演,在做题时我们也能更容易看出它的蛛丝马迹。

3.2.1. 将 mod 转化为求和

首先看一道经典的单位根反演入门题:给出 n,s,a0,a1,a2,a31018,求 i=0n(ni)siaimod4998244353 取模。

看到组合数和幂结合,自然想到二项式定理,可是 aimod4 的部分断绝了我们的出路 …… 吗?并没有。如果我们将 aimod4 写作 j=03aj[ij(mod4)],再使用单位根反演得到 j=03aj×14k=03ω4(ij)k,揉进原来的柿子:

14i=0n(ni)sij=03ajk=03ω4(ij)k

交换求和符号并整理,得

14j=03ajk=03ω4jki=0n(ni)(sω4k)i×1ni

逆用二项式定理,得

14j=03ajk=03ω4jk(sω4k+1)n

如果你学过 NTT 就知道在模 998244353 意义下,原根和单位根可以互换,因此这里的四次单位根等价于 gp14,快速幂解决。时间复杂度 O(logn)

代码见例题 I.

3.3. 例题

*I. LOJ#6485. LJJ 学二项式定理

代码

*II. P5591 小猪佩奇学数学

推柿子:根据 ik=iimodkk

1ki=0n(ni)×pi×(iimodk)

略去 1k,括号拆开分别考虑。

  • 前半部分:i=0n(ni)pi×i

    根据 (nm)=nm(n1m1),得 npi=0n(n1i1)pi1

    np(p+1)n1

  • 后半部分:略去负号,得 i=0n(ni)pij=0k1j[ij(modk)]

    套入单位根反演,得 i=0n(ni)pij=0k1jkx=0k1ωk(ij)x

    略去 1k,交换求和符号,得 j=0k1jx=0k1ωkjxi=0n(ni)(pωkx)i

    套入二项式定理,得 j=0k1jx=0k1ωkjx(pωkx+1)n

    然后发现不会了。交换求和顺序,得 x=0k1(pωkx+1)nj=0k1j(ωkx)j

    略去前面的柿子,将 ωkx 记做 c,仅关注 j=0k1jcj。由于 k 是常数,因此将其记做 f(c)。看到这里,你发现了什么?对!是我们小学二年级就学过的错位相减法!

    • 如果 c1,求 cf(c)f(c):比对每一项的系数,有 f(c)cf(c)=j=1k1cj(k1)ck

      先特判掉 k=1 的情况,然后用等比数列求和公式得到 j=1k1cj=1+j=0k1cj=1+ck1c1,注意到 ck 恒为 1,所以 f(c)=1(k1)1c=kc1

    • 如果 c=1,那么原式等于 j=0k1j=k(k1)2

    因此直接计算即可!时间复杂度 O(klogn)

ll n, p, k, w, ans, sub;
int main() {
	cin >> n >> p >> k, w = ksm(3, (mod - 1) / k);
	ans = n * p % mod * ksm(p + 1, n - 1) % mod;
	for(int x = 0; x < k; x++) {
		ll om = ksm(w, x), pw = ksm(p * om + 1, n), c = inv(om);
		if(c == 1) sub = (sub + pw * (k * (k - 1) / 2 % mod)) % mod;
		else sub = (sub + pw * k % mod * inv(mod + c - 1)) % mod;
	} cout << (ans + mod - sub * inv(k) % mod) * inv(k) % mod;
	return 0;
}

4. 莫比乌斯反演

极其重要的一类反演,对于推式子的帮助很大。

4.1. 前置知识

4.1.1. 数论函数与积性函数

数论函数,就是定义域是正整数的函数。

积性函数,就是对于任意 x,y\N+,若 gcd(x,y)=1,则 f(xy)=f(x)f(y) 的函数 f。若完全积性则不需要 gcd(x,y)=1 的条件。

积性函数举例(下文中会出现):

  • 单位函数 ϵ(n)=[n=1]
  • 常数函数 1(n)=1
  • 恒等函数 idk(n)=nkid1(n) 通常记作 id(n)
  • 除数函数 σk(n)=dndkσ0(n) 就是约数个数,记作 τ(n)d(n)σ1(n) 就是约数和,记作 σ(n)
  • 大名鼎鼎的欧拉函数 φ(n)=i=1n[gcd(i,n)=1]。关于欧拉函数可以看 基础数论学习笔记
  • 本章节的核心莫比乌斯函数 μ(n)={0\exist d>1,d2n(1)ω(n)otherwise,其中 ω(n) 表示 n 本质不同质因子个数。它的积性是显然的。

4.1.2. 狄利克雷卷积

由于学习莫比乌斯反演之前需要学会狄利克雷卷积,所以就放在这了。狄利克雷卷积竟沦落到给莫比乌斯反演作铺垫(大雾)。

定义:设 f,g 是数论函数,定义它们的狄利克雷卷积为 h(x)=a×b=xf(a)g(b),写作 h=fg。显然的,狄利克雷卷积具有交换律,结合律和乘法分配律。它的另一种表达形式为 h(x)=dxf(d)g(xd)非常有用

FMT 和 FWT 叫位运算卷积,FFT 和 NTT 叫加法卷积,不难发现狄利克雷卷积实际上是乘法卷积。它有如下性质:两个积性函数的狄利克雷卷积也是积性函数,积性函数的逆元也是积性函数。证明略。

4.1.3. 莫比乌斯函数

因为莫比乌斯函数是积性函数,所以它可以由线性筛求得。莫比乌斯函数有以下几条性质:

(4.1)dnμ(d)={1n=10n1

首先 n=1 时显然,否则我们令 n 的本质不同质因数分别为 p1,p2,,pk,从中选取 i 个质数的方案数为 (ki)。由选奇数个方案数之和等于选偶数个方案数之和可知和为 0k>0i=0k1ki×(ki)×(1)i=(1+(1))k=0(二项式定定理)。因而证明了 μ1=ϵ


(4.2)φ(n)=dnd×μ(nd)

该式子在推式子时比较有用。可以先证明 φ1=id,两边同时卷积 μφ(1μ)=φϵ=φ=idμ。得证。

变式:

(4.2.2)φ(n)n=dnμ(d)d

令上式两端同除 n 即得。


(4.3)dgcd(i,j)μ(d)=[gcd(i,j)=1]

极其重要的一个柿子,是莫比乌斯反演的核心。将 gcd(i,j) 带入式 4.1 即得。


线性筛莫比乌斯函数:根据 μ(n)×μ(p)=μ(n)=μ(np) (gcd(n,p)=1) 可得。

mu[1] = 1;
for(int i = 2; i < N; i++) {
	if(!vis[i]) pr[++cnt] = i, mu[i] = -1;
	for(int j = 1; j <= cnt && i * pr[j] < N; j++) {
		vis[i * pr[j]] = 1;
		if(i % pr[j] == 0) break;
		mu[i * pr[j]] = -mu[i];
	}
}

4.1.4. 整除分块

哪里有莫反,哪里就有整除分块。详见 基础数论学习笔记 Part 5 整除分块。

4.1.5. 线性筛积性函数

见本文 Part 6.1。

4.2. 公式

f,g 是两个数论函数,那么有

(4.4)f(n)=dng(d)g(n)=dnμ(d)f(nd)

证明:已知 g1=f,则 g1μ=g=fμ

(4.5)f(n)=ndg(d)g(n)=ndμ(dn)f(d)

证明(来自 OI - Wiki):

ndμ(dn)f(d)=k=1+μ(k)f(kn)=k=1+μ(k)kndg(d)=ndg(d)kdnμ(k)=ndg(d)ϵ(dn)=g(n)

4.3. 直观理解

只会套皮是不行的,要想熟练运用,不仅要多刷题,也要理解莫反的本质:容斥原理。其中莫比乌斯函数就是容斥系数。也许你会问:容斥系数不应该是 (1)k 吗?其实在某种程度上没错,毕竟对比 (1)k 与莫比乌斯函数的定义,它们的形式非常相近:只有当 x 是完全平方数的倍数时容斥系数才为 0(这其实也暗示了这样的 x 不同于别的数)。但我们是对于全体自然数做容斥,会有一些奇怪的事情发生:一个自然数被另一个自然数完全包含(这也说明它一定是完全平方数的倍数,接下来会解释),而在容斥原理中,一个集合被另一个集合包含这样的事情是不会发生的。

不妨设接下来有 fn=dngd

4.3.1. n=2

n=2 时,为了求出 g2,我们只需要用 f2f1=(g2+g1)g1。这里已经可以看见容斥的影子了。

4.3.2. n=6pi

n=6 时,为了求出 g6,我们首先需要 f6,但是这样会多加 g1+g2+g3,所以减去 f2+f3,但是这样又多减了 g1,所以我们还应加上 f1

如果 n 是若干不同质数 p1,p2,,pk 的乘积,画出能够表示 n 的所有约数的韦恩图(k>3 时画不出来,不过可以理解一下)。k 个集合相交形成的所有子集就是 n 的所有约数。比如说,如果有一块区域落在集合 p1 和集合 p3 中,那么它所表示的约数就是 p1p3。显然,如果 xy,那么区域 y 包含于区域 x

这里放上一张图,链接图源

结合上图,不妨将 fd 看做整块区域 d(例如上图中区域 30 也属于区域 6),而 gd 看做标上 d 的小区域,然后将所有区域表示的数从 i 替换成 ni,那么 fn 就表示最外层的大区域,即所有 gd 的和,而 f1 就表示最里层的小区域,即 g1,刚好符合 fn=dngd 的条件。因此,由于 f 已知,为了得到仅属于区域 n 的部分所表示的数 gn,我们只需要进行容斥:加上 fn,减去 fp1,,fpk,加上 fp1p2,fp1,p3,,fpk1pk ,以此类推。不难发现每个数前的容斥系数就是 1ni 所含质因子个数次幂,即 μ(ni)n 由互不相同的质因子相乘得到,那么 ni 亦然)。这不就是 gn=dnfdμ(nd) 吗!

4.3.3. n=4 及任意自然数

有了上面的经验,你应该知道表示约数 4 的区域被表示约数 2 的区域 “完全包含”。画出韦恩图就是一大一小两个圆,其中一个圆完全落在另一个里面。因此,如果计算了 2 的贡献,那么完全没有必要再算 4 了,因为 42 “完全包含”,所以它们的 “多加/多减状态” 应该是相同的。也就是说算完 2 的贡献,4 的贡献也恰好算完,既不多加,也不多减,因而 μ(4)=0。不难发现,这种情况仅在一个数是非 1 的完全平方数的倍数时出现,从而得以将莫比乌斯函数推广至任意自然数。

综上,莫比乌斯函数可以看作\N+ 做容斥时的容斥系数。结合 1μ=ϵ 这一性质,μ(n) 的定义变得十分自然:n 含有完全平方因子 p 时可以直接忽略不计,因为 n 的贡献在 np 处已经计算过。否则 n 含有的质因子个数就是其在容斥时所表示的集合大小,因为当 n=1 时需定义为 1μ(n)=(1)ω(n)

4.4. 应用

一般用来化简带有 gcd 的柿子,几乎所有莫反题都需要用到整除分块。具体应用可以看 OI-Wiki,抄在博客里其实意义不大。

4.5. 例题

下面所有分式都是下取整。有部分整除分块的题目。

I. P2522 [HAOI2011]Problem b

二维差分将下界化为 1,然后推式子:

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

k 搞掉:

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

莫反:

i=1nkj=1mkdgcd(i,j)μ(d)

枚举约数 d,记 c=min(nk,mk)

d=1cμ(d)i=1nk[di]j=1mk[dj]

由于 1xy 的倍数有 xy 个,故原式化为:

d=1cμ(d)nkdmkd

整除分块即可,时间复杂度 O(n+Tn)。long long 改成 int,11s 变成 5s,离谱。

II. SP5971 LCM Sum

来推式子。

i=1nlcm(i,n)=ni=1nigcd(i,n)

gcd 在分母的位置,很不爽,尝试枚举 gcd

 ni=1nidn1d[gcd(i,n)=d]= ndn1di=1ni[gcd(i,n)=d]= ndnddi=1ndi[gcd(i,nd)=1]= ndni=1di[gcd(i,d)=1]= ndnF(d)

单独算 F(d)=i=1di[gcd(i,d)=1]

 F(n)= i=1nidgcd(i,n)μ(d)= dnμ(d)i=1ndid= dnμ(d)d×nd(1+nd)2

至此,我们可以 nlnn 枚举 d 及其倍数 kd 算出所有 F(n),再根据 F(i) 枚举 d 及其倍数算出所有数的答案。时间复杂度 O(T+nlnn)。实际上可以继续化简:

 F(n)= n2dnμ(d)(1+nd)= n(ϵ(n)+φ(n))2

这里用到了 μ1=ϵ 以及 μid=φ。由于 F=id×(ϵ+φ)2,两部分都是积性函数,那么 1F 的两部分仍是积性函数,可以线性筛求出。时间复杂度 O(T+n)

const int N = 1e6 + 5;
ll T, pr[N], G[N], low[N], cnt;
bool vis[N];
void sieve() {
	G[1] = 1;
	for(int i = 2; i < N; i++) {
		if(!vis[i]) pr[++cnt] = i, G[i] = 1 + 1ll * i * (i - 1), low[i] = i;
		for(int j = 1; j <= cnt && i * pr[j] < N; j++) {
			vis[i * pr[j]] = 1;
			if(i % pr[j] == 0) {
				low[i * pr[j]] = low[i] * pr[j];
				if(i == low[i]) G[i * pr[j]] = G[i] + low[i] * (pr[j] - 1) * low[i] * pr[j];
				else G[i * pr[j]] = G[i / low[i]] * G[low[i] * pr[j]]; break;
			} G[i * pr[j]] = G[i] * G[pr[j]], low[i * pr[j]] = pr[j];
		}
	}
}
int main(){
	cin >> T, sieve();
	while(T--) {
		int n = read();
		print((G[n] + 1) * n / 2), pc('\n');
	} return flush(), 0;
}

*III. P4318 完全平方数

莫比乌斯函数作容斥系数的经典例子。答案满足可二分性,于是考虑如何求 x 以内不含有平方因子的数的个数。仍然是容斥:我们只需加上所有由偶数个质数相乘的数的平方的倍数,减去由奇数个质数相乘的数的平方的倍数。发现就是 i2nμ(i)ni2,直接暴力求。时间复杂度 O(Tlogkk)

const int N = 1e5 + 5;
int T, n, cnt, pr[N], vis[N], mu[N];
int main() {
	cin >> T, mu[1] = 1;
	for(int i = 2; i < N;i++) {
		if(!vis[i]) pr[++cnt] = i, mu[i] = -1;
		for(int j = 1; j <= cnt && i * pr[j] < N; j++) {
			vis[i * pr[j]] = 1;
			if(i % pr[j] == 0) break;
			mu[i * pr[j]] = -mu[i];
		}
	} while(T--){
		cin >> n; ll l = n, r = n << 1;
		while(l < r) {
			ll m = l + r >> 1, res = 0;
			for(int i = 1; i * i <= m; i++) res += mu[i] * (x / i / i);
			res < n ? l = m + 1 : r = m;
		} cout << l << endl;
	} return 0;
}

IV. P2257 YY 的 GCD

考虑枚举 gcd(i,j)=pP[2,min(n,m)]

 i=1nj=1m[gcd(i,j)P]= pPi=1nj=1m[gcd(i,j)=p]= pPi=1npi=1mp[gcd(i,j)=1]= pPi=1npi=1mpdgcd(i,j)μ(d)= pPd=1min(np,mp)μ(d)npdmpd

做到这里发现推不动了,因为我们不得不枚举 pP。一个 trick 是pd 写成 T,并将 pd 写成狄利克雷卷积的形式:

T=1min(n,m)pTpPTnTmTμ(Tp)

为什么要这样做:分母上的 pd 与两个变量有关,换种写法仅与一个变量有关,于是可以提前,但并没有改变求 μ 的本质,只是调整了计算顺序使得可通过乘法分配律提出向下取整的柿子。或者说,对于使 pd 相同的 p,d 取值,μ(d) 的形式可以预处理避免重复计算。这个大概要写过很多莫反题目才能来感觉。

*另一种推导方式:考虑对 [gcd(i,j)=p] 进行容斥并用莫比乌斯函数作容斥系数:i=1nj=1md=kpμ(k)[dgcd(i,j)],相当于 μ 乘上 ϵp(i)[i=p] (pP),然后求和。稍作化简得到 d=1min(n,m)ndmdpdpPμ(np),发现与上述柿子等价。

f(T)=pTpPμ(Tp) 可以通过类似埃氏筛的筛法 nloglogn 求出,前缀和之后整除分块即可。总时间复杂度 O(Tn+nloglogn)f(T) 还可以线性筛,具体推导过程略去,复杂度 O(Tn+n)​。

const int N = 1e7 + 5;
int T, n, m, cnt, vis[N], pr[N >> 3], f[N], mu[N];
int main(){
	for(int i = 2; i < N; i++) {
		if(!vis[i]) pr[++cnt] = i, f[i] = 1, mu[i] = -1;
		for(int j = 1; j <= cnt && i * pr[j] < N; j++) {
			vis[i * pr[j]] = 1;
			if(i % pr[j] == 0) {f[i * pr[j]] = mu[i]; break;}
			f[i * pr[j]] = -f[i] + mu[i], mu[i * pr[j]] = -mu[i];
		}
	} for(int i = 2; i < N; i++) f[i] += f[i - 1]; cin >> T;
	while(T--) {
		nll ans = 0 cin >> n >> m;
		for(int l = 1, r; l <= min(n, m); l = r + 1) {
			r = min(n / (n / l), m / (m / l));
			ans += 1ll * (n / l) * (m / l) * (f[r] - f[l - 1]);
		} cout << ans << "\n";
	}
	return 0;
}

V. P3455 [POI2007]ZAP-Queries

双倍经验 P2522。

VI. P2568 GCD

双倍经验 P2257.

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

 i=1nj=1mlcm(i,j)= i=1nj=1mijgcd(i,j)= d=1min(n,m)1di=1ndj=1mdijd2kgcd(i,j)μ(k)= d=1min(n,m)dk=1min(ndmd)μ(k)k2i=1nkdij=1mkdj= d=1min(n,m)dk=1min(ndmd)μ(k)k2S(nkd)S(mkd)

已经可以 nlnn 做了,但是还不够。到这里我们可以看到和 IV. 差不多的套路,令 Tkd

= T=1min(n,m)S(nT)S(mT)kTμ(k)k2Tk

注意到前面一坨可以整除分块,后面相当于求 f(T) 其中 f=(μ×id2)id(化简一下也可以看成 T×f(T) 其中 f=μid),是积性函数,可以线筛求出。时间复杂度 O(n)。实际上可以多组询问。

const int N = 1e7 + 5;
const int mod = 20101009;
int n, m, c, pr[N >> 3], cnt, low[N];
bool vis[N]; ll f[N], ans;
ll S(ll x) {return x * (x + 1) / 2 % mod;}
int main(){
	cin >> n >> m, c = min(n, m), f[1] = 1;
	for(ll i = 2; i <= c; i++) {
		if(!vis[i]) low[i] = pr[++cnt] = i, f[i] = (i - i * i % mod + mod) % mod;
		for(int j = 1; j <= cnt && i * pr[j] <= c; j++) {
			int k = i * pr[j]; vis[k] = 1;
			if(i % pr[j] == 0) {
				low[k] = low[i] * pr[j];
				if(low[i] == i) f[k] = (k - pr[j] * pr[j] % mod * i % mod + mod) % mod;
				else f[k] = f[i / low[i]] * f[low[k]] % mod; break;
			} low[k] = pr[j], f[k] = f[i] * f[pr[j]] % mod;
		}
	}
	for(int i = 2; i <= c; i++) f[i] = (f[i] + f[i - 1]) % mod;
	for(int l = 1, r; l <= c; l = r + 1) {
		r = min(n / (n / l), m / (m / l));
		ans = (ans + S(n / l) * S(m / l) % mod * (f[r] - f[l - 1] + mod)) % mod;
	} cout << ans << endl;
	return 0;
}

VIII. AT5200 [AGC038C] LCMs

原题的两个枚举上界都写到 n 方便求答案,那么需要将结果减掉 ai 后除以 2。记 bi 表示 i{aj} 中的出现次数,有:

 i=1Vj=1Vbibj×lcm(i,j)= i=1vj=1vij×bibjgcd(i,j)

剩下来推式子的过程和上一题差不多,但是因为有 bi 的存在所以无法写成一个简单式子的形式。注意推到  d=1V1dk=1Vdμ(k)S2(kd) 其中 S(i) 表示 ki×bki (k\Z) 的时候可以 VlnV 做,时间复杂度 O(n+VlnV)。运用 Part 5 所讲解的狄利克雷前 / 后缀和并改变枚举顺序线性筛 1idμ 可以 O(n+VloglogV),即 T=1VS2(T)dT1dμ(Td)。为了卡常可以写成 T=1VS2(T)1TdTTdμ(Td),这样就只需要最后求答案时取模。

const int N = 1e6 + 5;
const int mod = 998244353;
int n, f[N], cnt, mx, pr[N >> 3];
ll b[N], inv[N], ans; bool vis[N];
int main(){
	cin >> n, f[1] = inv[1] = 1;
	for(int i = 1, x; i <= n; i++) cmax(mx, x = read()), ans -= x, b[x] += x;
	for(int i = 2; i <= mx; i++) {
		inv[i] = mod - mod / i * inv[mod % i] % mod;
		if(!vis[i]) pr[++cnt] = i, f[i] = 1 - i;
		for(int j = 1; j <= cnt && i * pr[j] <= mx; j++) {
			int k = i * pr[j]; vis[k] = 1;
			if(i % pr[j] == 0) {f[k] = f[i]; break;}
			f[k] = f[i] * f[pr[j]];
		}
	} for(int i = 1; i <= cnt; i++) for(int j = mx / pr[i]; j; j--) b[j] += b[j * pr[i]];
	for(int i = 1; i <= mx; i++) b[i] %= mod, ans += b[i] * b[i] % mod * f[i] % mod * inv[i] % mod;
	cout << (ans % mod + mod) % mod * inv[2] % mod << endl;
	return 0;
}

IX. P3911 最小公倍数之和

双倍经验。

X. P6156 简单题

 i=1nj=1n(i+j)kd=1nμ2(d)d[gcd(i,j)=d]= d=1nμ2(d)dk+1i=1ndj=1nd(i+j)k[gcd(i,j)=1]= d=1nμ2(d)dk+1o=1ndμ(o)okindojndo(i+j)k= T=1nF(nT)dTμ2(d)dk+1μ(Td)(Td)k= T=1nF(nT)TkdTμ2(d)dμ(Td)

其中 F(n) 表示 i=1nj=1n(i+j)k。直接线性筛 (μ2×id)μ 以及 pk 即可,时间复杂度 O(n+nlnn×logk)O(n)

ll k;
int low[N], kp[N], F[N >> 1], G[N];
int n, cnt, ans, pr[N / 15];
bitset <N> vis;
int main(){
	cin >> n >> k, k %= mod - 1, kp[1] = 1, G[1] = 1;
	for(int i = 2; i <= n << 1; i++) {
		if(!vis[i]) kp[i] = ksm(i, k), pr[++cnt] = low[i] = i, G[i] = i - 1;
		for(int j = 1; j <= cnt && i * pr[j] <= n << 1; j++) {
			int c = i * pr[j]; vis[c] = 1, kp[c] = 1ll * kp[i] * kp[pr[j]] % mod;
			if(i % pr[j] == 0) {
				low[c] = low[i] * pr[j];
				if(i == low[i]) G[c] = i == pr[j] ? mod - pr[j] : 0;
				else G[c] = 1ll * G[i / low[i]] * G[low[c]] % mod; break;
			} low[c] = pr[j], G[c] = 1ll * G[i] * G[pr[j]] % mod;
		}
	} for(int i = 2; i <= n; i++) G[i] = (G[i - 1] + 1ll * G[i] * kp[i]) % mod;
	for(int i = 1; i <= n << 1; i++) kp[i] = (kp[i - 1] + kp[i]) % mod;
	for(int i = 1, j = 2; i <= n; i++, j += 2) F[i] = (F[i - 1] + 2ll * (kp[j] - kp[i] + mod) - (kp[j] - kp[j - 1]) + mod) % mod;
	for(int l = 1, r; l <= n; l = r + 1) r = n / (n / l), ans = (ans + 1ll * F[n / l] * (G[r] - G[l - 1] + mod)) % mod;
	cout << ans << endl;
	return 0;
}

XI. P6222 「P6156 简单题」加强版

双倍经验。

5. Dirichlet 卷积

6. 高级筛法

6.1. 线性筛积性函数

前置知识:对线性筛的工作原理有一定认识。

众所周知,欧拉筛在最小质因子处筛去所有合数,但需要讨论:1. 最小质因子次数 =1。2. 最小质因子次数 >1。了解两种情况进入的不同分支有助于理解线筛积性函数。

for(int i = 2; i < N; i++) {
	if(!vis[i]) pr[++cnt] = i;
	for(int j = 1; j <= cnt && i * pr[j] < N; j++) {
		vis[i * pr[j]] = 1;
		if(i % pr[j] == 0) break; // i * pr[j] 最小质因子 pr[j] 次数 > 1
		// i * pr[j] 最小质因子 pr[j] 次数 = 1
	}
}

并不是所有积性函数 f 都可以线筛求出,它需要满足:根据 f(pk) 可快速求出 f(pk+1)​。当 i×p 最小质因子 p 的次数为 1 时,方便求得 f(ip)=f(i)f(p)。但不为 1 时需要另辟蹊径:若 i=pk 则根据 f(i) 推得 f(ip)f(pk+1)。 否则设 i 含有 c 个质因子 p,根据积性函数的性质与已经求得的 f(pc+1)(因为 i>pc,在 i=pc 时已经求过 f(pc+1))容易得到 f(ip)=f(ipc)f(pc+1)。对此还需记录 lowi 表示 i 最小质因子 ppc

for(int i = 2; i < N; i++) {
	if(!vis[i]) pr[++cnt] = i, f[i] = ..., low[i] = i;
	for(int j = 1; j <= cnt && i * pr[j] < N; j++) {
		vis[i * pr[j]] = 1;
		if(i % pr[j] == 0) { // 此时 i 与 pr 不互质, 需要另辟蹊径
			low[i * pr[j]] = low[i] * pr[j];
			if(i == low[i]) f[i * pr[j]] = ...; // 根据 f(p ^ k) 算 f(p ^ (k + 1))
			else f[i * pr[j]] = f[i / low[i]] * f[low[i * pr[j]]]; break;
		} f[i * pr[j]] = f[i] * f[pr[j]], low[i * pr[j]] = pr[j]
		// i * pr 最小质因子次数 = 1, 可以通过 f[i] * f[pr] 算 f[i * pr] (积性函数的性质)
	}
}
posted @   qAlex_Weiq  阅读(3494)  评论(5编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示