莫比乌斯函数
定义
莫比乌斯函数的定义如下:
μ(n)=⎩⎨⎧0(−1)k∃ p2∣n∧p>1n=∏i=1kpi
也就是说,如果 n 含有平方约数,则 μ(n)=0,否则 μ(n)=(−1)k,其中 k 为 n 中的本质不同的质约数个数。
性质
积性函数
μ(n) 为积性函数,证明如下:
设 a,b 满足 gcd(a,b)=1。
- 若 a,b 其中一个数含有平方约数,则它们的乘积也一定含有平方约数,此时它们的莫比乌斯函数值都为 0,即 μ(ab)=μ(a)μ(b)=0。
- 否则 a,b 都不含有平方约数,则设它们的质约数个数分别为 ka,kb,则 μ(a)=(−1)ka,μ(b)=(−1)kb,因为满足 gcd(a,b)=1,故 ab 的质约数个数为 ka+kb,μ(ab)=(−1)ka+kb=(−1)ka(−1)kb=μ(a)μ(b)。
Q.E.D.
与常数函数的卷积
这个性质十分重要,是反演的基础。这个性质可以写成 I∗μ=ϵ,或者:
d∣n∑I(dn)μ(d)=ϵ(n)
经常简记为:
d∣n∑μ(d)=[n=1]
ϵ 是 Dirchlet 卷积的单位元函数。
证明如下:
考虑 d 作为 n 的约数,其自身对整体的贡献,设 n,d 的唯一分解分别为 n=∏i=1kpici,d=∏i=1kpici′。
- 若 ∃ pi 满足 pi2∣d,则 μ(d)=0,容易发现这样的 d 的唯一分解中,存在一个 ci′>1。
- 若 n=1,则 k=0,对答案有贡献的 d 一定满足 ∀ i∈[1,k],ci∈[0,1],也就是每个 pi 选一次或不选。枚举 d 选的质约数个数 i:
i=0∑k(−1)i(ik)=i=0∑k(ik)1k−i(−1)i=[1+(−1)]k=0
- 特殊考虑 n=1,此时 k=0:
i=0∑k(−1)i(ik)=(−1)0(00)=1
综上,∑d∣nμ(d)=∑i=0k(−1)i(ik)=[n=1]
Q.E.D.
容斥系数
μ(n) 的本质其实是容斥系数,比如这个问题:长度为 n 的整数序列,每个数的取值范围为 [1,m],求满足序列元素的 gcd 为 1 的序列个数。
直接计算比较难,可以用容斥原理,先设 f(x) 为序列元素的 gcd 为 x 的倍数的序列个数,再考虑重复。显然 f(x)=⌊xm⌋n。假设我们用全集的大小 f(1) 减去不合法的部分,对 f 的自变量取值进行讨论:
- 若 x=x0k 且 k>1,显然 x 是 x0 的倍数且 x0 对应的序列集合包含了 x 的情况,所以 x 不需要再被重复计算。
- 若 x=ab,则 a,b 对应的序列集合包含了 x 的情况
- 若 a,b 对答案都有贡献,则 x 的贡献被多算了一次
- 若 a,b 其中一个对答案都有贡献,则 x 没有被重复计算
- 若 a,b 其中对答案都没有贡献,则 x 也不需要对答案有贡献
- 若 x=abc,则 a,b,c 对应的序列集合包含了 x 的情况
- 若 a,b,c 对答案都有贡献,根据情况 2,x 对应的序列集合含于 a,b,c,ab,bc,ab 中,而 ab,bc,ab 都把 a,b,c 的集合中的 x 的情况扣掉了,所以现在要把 x 的贡献加回去。
- 若 a,b,c 其中一个对答案都没有贡献,则同情况 2,不需要加贡献
- 若仅 a,b,c 其中一个对答案都有贡献,则同情况 1,不需要加贡献
- 若 a,b,c 其中对答案都没有贡献,则 x 也不需要对答案有贡献
- etc.
可以看出一个规律:
- 若 x 为某个数的贡献且不等于那个数,则相当于含有平方约数,可以相当于它的贡献乘 0
- 否则考虑它能够拆成 x=abc⋯,若其中一个的贡献为 0,则 x 本身也不需要对答案有贡献,也就是其中一个数有平方约数,它就不需要有贡献
- 若都有 a,b,c,… 都有贡献,则贡献是正还是负,取决于 a,b,c,… 的个数,为奇数则为负贡献,否则为正贡献。
这与 μ(n) 的定义完全一致,所以我们完全可以把 μ(n) 当成容斥系数。
莫比乌斯反演
反演公式
约数反演公式
f(n)=d∣n∑g(d)⟹g(n)=d∣n∑μ(dn)f(d)
倍数反演公式
f(n)=n∣d∑g(d)⟹g(n)=n∣d∑μ(nd)f(d)
常见结论
ϵ(n)=d∣n∑μ(d)φ(n)=d∣n∑μ(dn)d=d∣n∑μ(d)dnd(n)=x∣n∑I(xn)I(x)=x∣n∑1σ(n)=d∣n∑I(dn)d=d∣n∑d
或者是 Dirchlet 卷积形式:
ϵ=I∗μφ=μ∗idd=I∗Iσ=I∗id
应用
如果一个函数 g(n) 难以计算,但知道了它与另一个函数 f(x) 的关系,如 Dirchlet 卷积的形式,则可以通过莫比乌斯反演得到。或者可以通过莫比乌斯反演对式子进行变换化简,方便计算。
例题
Luogu P2522 Problem b
对于给出的 n 个询问,每次求有多少个数对 (x,y),满足 a≤x≤b,c≤y≤d,且 gcd(x,y)=k。
1≤n,k≤5×104,1≤a≤b≤5×104,1≤c≤d≤5×104。
设 S(n,m)=∑i=1n∑j=1m[gcd(i,j)=k],则每次询问的答案为 S(b,d)−S(a−1,d)−S(b,c−1)+S(a−1,c−1)。接下来进行 S(n,m) 的推导:
S(n,m)=i=1∑nj=1∑m[gcd(i,j)=k]=i=1∑nj=1∑m[gcd(ki,ki)=1]=i=1∑⌊kn⌋j=1∑⌊km⌋[gcd(i,j)=1]
注意到 [gcd(i,j)=1] 与 ϵ(gcd(i,j)) 等价,用 ϵ(gcd(i,j)) 替换:
S(n,m)=i=1∑⌊kn⌋j=1∑⌊km⌋[gcd(i,j)=1]=i=1∑⌊kn⌋j=1∑⌊km⌋ϵ(gcd(i,j))=i=1∑⌊kn⌋j=1∑⌊km⌋d∣gcd(i,j)∑μ(d)
变换求和顺序,先枚举 d,则 d∣gcd(i,j),也就是 d∣i∧d∣j,则后续枚举 i,j 也要满足能够被 d 整除:
S(n,m)=i=1∑⌊kn⌋j=1∑⌊km⌋d∣gcd(i,j)∑μ(d)=d=1∑min(⌊kn⌋,⌊km⌋)μ(d)i=1∑⌊kn⌋[d∣i]j=1∑⌊km⌋[d∣j]=d=1∑min(⌊kn⌋,⌊km⌋)μ(d)⌊d⌊kn⌋⌋⌊d⌊km⌋⌋=d=1∑min(⌊kn⌋,⌊km⌋)μ(d)⌊dkn⌋⌊dkm⌋
后面的 ⌊dkn⌋⌊dkm⌋ 用数论分块优化,这个式子就仅有 O(n+m) 种取值,且相同的取值都在连续的一段区间,所以可以预处理 ∑d=1min(n,m)μ(d),每一段区间就是 μ(d) 的区间和乘后面的式子。
认为 a,b,c,d 同阶,则时间复杂度 O(a+na)。
#include <iostream>
using namespace std;
constexpr int MAX_N = 5e4;
int n, t, mu[MAX_N + 10], prime[MAX_N + 10], notPrime[MAX_N + 10];
void sieve(int n) {
mu[1] = 1;
notPrime[1] = 1;
for (int i = 1; i <= n; ++i) {
if (!notPrime[i]) {
prime[++t] = i;
mu[i] = -1;
}
for (int j = 1; j <= t; ++j) {
if (i * prime[j] > n)
break;
notPrime[i * prime[j]] = true;
mu[i * prime[j]] = i % prime[j] == 0 ? 0 : -mu[i];
if (i % prime[j] == 0)
break;
}
}
for (int i = 1; i <= n; ++i)
mu[i] += mu[i - 1];
}
// 这里其实算的是 gcd(i, j) = 1 的个数
// 也就是 S(n, m) = calc(n / k, m / k)
int calc(int n, int m) {
int res = 0;
for (int l = 1, r; l <= min(n, m); l = r + 1) {
r = min(n / (n / l), m / (m / l));
res += (mu[r] - mu[l - 1]) * (n / l) * (m / l);
}
return res;
}
int main() {
sieve();
cin >> n;
for (int i = 1; i <= n; ++i) {
int a, b, c, d, k, res = 0;
cin >> a >> b >> c >> d >> k;
res += calc(b / k, d / k);
res -= calc(b / k, (c - 1) / k);
res -= calc((a - 1) / k, d / k);
res += calc((a - 1) / k, (c - 1) / k);
cout << res << endl;
}
return 0;
}
Luogu P2257 YY 的 GCD
给出 n,m,求满足 1≤x≤n,1≤y≤m 且 gcd(x,y) 为质数的有序数对 (x,y) 个数。
有 t 组数据,1≤t≤104,1≤n,m≤107。
设质数集为 P,则我们要求出以下式子:
i=1∑nj=1∑m[gcd(i,j)∈P]
考虑枚举 p=gcd(i,j)∈P,则:
i=1∑nj=1∑m[gcd(i,j)∈P]=p∈P∑i=1∑nj=1∑m[gcd(i,j)=p]=p∈P∑d=1∑min(⌊pn⌋,⌊pm⌋)μ(d)⌊dpn⌋⌊dpm⌋
这样子做绝对会 TLE,因为如果使用这个形式进行计算,必须要枚举 p,所以进行优化,设 t=dp,则 d=pt,所以式子变成这样:
i=1∑nj=1∑m[gcd(i,j)∈P]=p∈P∑d=1∑min(⌊pn⌋,⌊pm⌋)μ(d)⌊dpn⌋⌊dpm⌋=p∈P∑p∣t∑μ(pt)⌊tn⌋⌊tm⌋=t=1∑min(n,m)⌊tn⌋⌊tm⌋p∣t∑μ(pt)
可以把后面的 ∑p∣tμ(pt) 看成一个与 t 有关的函数,那么我们就可以通过枚举每个质数 p,让它去贡献它的每一个倍数,这个可以 O(nlog2n) 预处理,再 O(n) 预处理前缀和,然后每次询问就可以直接数论分块了。
同样认为 n,m 同阶,则时间复杂度 O(nlog2n+tn)。
#include <iostream>
using namespace std;
constexpr int MAX_N = 1e7 + 10;
int prime[MAX_N], notPrime[MAX_N], mu[MAX_N];
long long sum[MAX_N];
int n, m, t, l;
void sieve() {
notPrime[0] = notPrime[1] = true;
mu[1] = 1;
for (int i = 2; i < MAX_N; ++i) {
if (!notPrime[i]) {
prime[++l] = i;
mu[i] = -1;
}
for (int j = 1; j <= l; ++j) {
if (i * prime[j] > MAX_N - 1)
break;
notPrime[i * prime[j]] = true;
mu[i * prime[j]] = i % prime[j] ? -mu[i] : 0;
if (i % prime[j] == 0)
break;
}
}
for (int i = 1; i <= l; ++i)
for (int j = 1; prime[i] * j < MAX_N; ++j)
sum[prime[i] * j] += mu[j];
for (int i = 2; i < MAX_N; ++i)
sum[i] += sum[i - 1];
}
long long calc(int n, int m) {
long long res = 0;
for (int l = 1, r; l <= min(n, m); l = r + 1) {
r = min(n / (n / l), m / (m / l));
res += (sum[r] - sum[l - 1]) * (n / l) * (m / l);
}
return res;
}
int main() {
sieve();
cin >> t;
for (int i = 1; i <= t; ++i) {
cin >> n >> m;
cout << calc(n, m) << endl;
}
return 0;
}
Luogu P3704 数字表格
设 fi 为斐波那契数列的第 i 项,求:
>i=1∏nj=1∏mfgcd(i,j)mod(109+7)>
t 组数据,1≤t≤103,1≤n,m≤106。
先枚举 d 作为 gcd(i,j),考虑每个 f(d) 会被乘几次,也就是把式子化成这样:
d=1∏min(n,m)f(d)∑i=1n∑j=1m[gcd(i,j)=d]
首先可以化简指数,设指数为 t(d):
t(d)=k=1∑min(⌊dn⌋,⌊dm⌋)μ(k)⌊dkn⌋⌊dkm⌋d=1∏min(n,m)f(d)∑i=1n∑j=1m[gcd(i,j)=d]=d=1∏min(n,m)f(d)t(d)
现在已经可以使用数论分块套数论分块 A 了这道题了,只要再预处理一个前缀积及其逆元,时间复杂度 O(n+tn43log2n)。你没有看错,可以 Θ(n) 预处理斐波那契数列的前缀积的逆元。
这个算法跑得巨慢,实现不好就会 TLE。进行用上面的技巧进行优化,设 p=dk,则 k=dp,则:
t(d)=d∣p∑μ(dp)⌊pn⌋⌊pm⌋
先枚举 p,现在 p 从指数上变到了底数上,所以加变为乘,也就是变换求积顺序:
d=1∏min(n,m)f(d)t(d)=p=1∏min(n,m)d∣p∏f(d)μ(dp)⌊pn⌋⌊pm⌋=p=1∏min(n,m)d∣p∏f(d)μ(dp)⌊pn⌋⌊pm⌋
所以现在只要预处理出 ∏d∣pf(d)μ(dp),再做前缀积,很明显可以 O(nlog2n) 预处理这个式子,用上文的贡献法实现即可。
至于求斐波那契数列前缀积的逆元的方法,其实适用于任意的正整数序列。具体可以看这里。
#include <iostream>
using namespace std;
constexpr int MAX_N = 1e6 + 10;
constexpr int MOD = 1e9 + 7;
int t, n, m;
int prime[MAX_N], mu[MAX_N], tot;
bool notPrime[MAX_N];
int fib[MAX_N], ifib[MAX_N];
int prod[MAX_N], iprod[MAX_N];
int tmp[MAX_N];
int power(int x, int y) {
int res = 1;
for (; y; y /= 2) {
if (y % 2)
res = 1ll * res * x % MOD;
x = 1ll * x * x % MOD;
}
return res;
}
void preprocessMu(int n) {
notPrime[1] = true;
mu[1] = 1;
for (int i = 2; i <= n; ++i) {
if (!notPrime[i]) {
prime[++tot] = i;
mu[i] = -1;
}
for (int j = 1; j <= tot && i * prime[j] <= n; ++j) {
notPrime[i * prime[j]] = true;
if (i % prime[j]) {
mu[i * prime[j]] = -mu[i];
} else {
mu[i * prime[j]] = 0;
break;
}
}
}
}
void preprocessFib(int n) {
fib[1] = 1;
for (int i = 2; i <= n; ++i)
fib[i] = (fib[i - 1] + fib[i - 2]) % MOD;
tmp[0] = 1;
for (int i = 1; i <= n; ++i)
tmp[i] = 1ll * tmp[i - 1] * fib[i] % MOD;
int itmp = power(tmp[n], MOD - 2);
for (int i = n; i >= 1; --i) {
ifib[i] = 1ll * itmp * tmp[i - 1] % MOD;
itmp = 1ll * itmp * fib[i] % MOD;
}
}
void preprocessProd(int n) {
for (int i = 1; i <= n; ++i)
prod[i] = 1;
for (int i = 1; i <= n; ++i) {
if (!mu[i])
continue;
if (mu[i] == 1) {
for (int j = i, k = 1; j <= n; j += i, ++k)
prod[j] = 1ll * prod[j] * fib[k] % MOD;
} else {
for (int j = i, k = 1; j <= n; j += i, ++k)
prod[j] = 1ll * prod[j] * ifib[k] % MOD;
}
}
prod[0] = 1;
for (int i = 1; i <= n; ++i) {
tmp[i] = prod[i];
prod[i] = 1ll * prod[i - 1] * prod[i] % MOD;
}
iprod[n] = power(prod[n], MOD - 2);
for (int i = n - 1; i >= 0; --i)
iprod[i] = 1ll * iprod[i + 1] * tmp[i + 1] % MOD;
}
void solve() {
int res = 1;
for (int l = 1, r; l <= min(n, m); l = r + 1) {
r = min(n / (n / l), m / (m / l));
res = 1ll * res * power(1ll * prod[r] * iprod[l - 1] % MOD, 1ll * (n / l) * (m / l) % (MOD - 1)) % MOD;
}
cout << res << endl;
}
int main() {
ios::sync_with_stdio(false);
preprocessMu(MAX_N - 10);
preprocessFib(MAX_N - 10);
preprocessProd(MAX_N - 10);
cin >> t;
while (t--) {
cin >> n >> m;
solve();
}
return 0;
}
SPOJ GCDMAT GCD OF MATRIX
给出 n,m,并给出 t 组询问,每组询问给出 i1,j1,i2,j2,求:
>i=i1∑i2j=j1∑j2gcd(i,j)>
1≤t≤500,1≤n,m≤5×104,1≤i1≤i2≤n,1≤j1≤j2≤m。
先设 S(n,m)=∑i=1n∑i=1mgcd(i,j),这是一个比较套路的式子,用到了 φ=μ∗id 的结论:
S(n,m)=d=1∑min(n,m)di=1∑nj=1∑m[gcd(i,j)=d]=d=1∑min(n,m)dk=1∑min(⌊dn⌋,⌊dm⌋)μ(k)⌊dkn⌋⌊dkm⌋
设 p=dk:
S(n,m)=d=1∑min(n,m)dk=1∑min(⌊dn⌋,⌊dm⌋)μ(k)⌊dkn⌋⌊dkm⌋=d∑min(n,m)dd∣p∑μ(dp)⌊pn⌋⌊pm⌋=p=1∑min(n,m)⌊pn⌋⌊pm⌋d∣p∑μ(dp)d=p=1∑min(n,m)φ(p)⌊pn⌋⌊pm⌋
时间复杂度 O(n+tn)。
#include <iostream>
using namespace std;
constexpr int MAX_N = 1e6 + 10;
constexpr int MOD = 1e9 + 7;
int t, n, m, i1, i2, j1, j2;
int prime[MAX_N], phi[MAX_N], tot;
bool notPrime[MAX_N];
void preprocess(int n) {
notPrime[1] = 1;
phi[1] = 1;
for (int i = 2; i <= n; ++i) {
if (!notPrime[i]) {
prime[++tot] = i;
phi[i] = i - 1;
}
for (int j = 1; j <= tot && i * prime[j] <= n; ++j) {
notPrime[i * prime[j]] = true;
if (i % prime[j]) {
phi[i * prime[j]] = phi[i] * (prime[j] - 1);
} else {
phi[i * prime[j]] = phi[i] * prime[j];
break;
}
}
}
for (int i = 2; i <= n; ++i) {
phi[i] = phi[i - 1] + phi[i];
if (phi[i] >= MOD)
phi[i] -= MOD;
}
}
int calc(int n, int m) {
int res = 0;
for (int l = 1, r, e = min(n, m), val; l <= e; l = r + 1) {
r = min(n / (n / l), m / (m / l));
val = 1ll * (n / l) * (m / l) % MOD;
res = (res + 1ll * (phi[r] - phi[l - 1] + MOD) * val) % MOD;
}
return res;
}
int main() {
ios::sync_with_stdio(false);
cin >> t;
cin >> n >> m;
preprocess(min(n, m));
while (t--) {
cin >> i1 >> j1 >> i2 >> j2;
long long ans = calc(i2, j2);
ans -= calc(i1 - 1, j2);
ans -= calc(i2, j1 - 1);
ans += calc(i1 - 1, j1 - 1);
cout << (ans % MOD + MOD) % MOD << endl;
}
return 0;
}
Luogu P6156 简单题
给出 n,k,求:
>i=1∑nj=1∑n(i+j)kf(gcd(i,j))gcd(i,j)>f(n)=>⎩⎨⎧>0>1∃ p2∣n∧p>1otherwise>>
1≤n≤5×106,1≤k≤1018。
首先容易发现 f(n)=μ2(n)。设答案为 ans,先枚举 d=gcd(i,j):
ans=i=1∑nj=1∑n(i+j)kμ2(gcd(i,j))gcd(i,j)=d=1∑nμ2(d)di=1∑nj=1∑n(i+j)k[gcd(i,j)=d]=d=1∑nμ2(d)dk+1i=1∑nj=1∑n(di+dj)k[gcd(di,dj)=1]=d=1∑nμ2(d)dk+1i=1∑⌊dn⌋j=1∑⌊dn⌋(i+j)k[gcd(i,j)=1]=d=1∑nμ2(d)dk+1t=1∑⌊dn⌋μ(t)i=1∑⌊dn⌋j=1∑⌊dn⌋(i+j)k[t∣i][t∣j]=d=1∑nμ2(d)dk+1t=1∑⌊dn⌋μ(t)tki=1∑⌊dn⌋j=1∑⌊dn⌋(ti+tj)k[t∣i][t∣j]=d=1∑nμ2(d)dk+1t=1∑⌊dn⌋μ(t)tki=1∑⌊dtn⌋j=1∑⌊dtn⌋(i+j)k
设 f(n)=∑i=1n∑j=1n(i+j)k,p=dt:
ans=d=1∑nμ2(d)dk+1t=1∑⌊dn⌋μ(t)tki=1∑⌊dtn⌋j=1∑⌊dtn⌋(i+j)k=d=1∑nμ2(d)dk+1t=1∑⌊dn⌋μ(t)tkf(⌊dtn⌋)=p=1∑nf(⌊pn⌋)d∣p∑μ2(d)μ(dp)dk+1(dp)k=p=1∑nf(⌊pn⌋)pkd∣p∑μ2(d)μ(dp)d
设 g(n)=∑d∣nμ2(d)μ(dn)d:
ans=p=1∑nf(⌊pn⌋)g(p)pk=p=1∑nf(⌊pn⌋)g(p)idk(p)
这个式子当中,出现了三个函数,依次考虑怎么求它们。
求 f(n)
对于 f(n)=∑i=1n∑j=1n(i+j)k,有两种方法:
方法 1:考虑作为底数的 i+j 的每个值出现了多少次,i+j∈[2,2n],为了方便,我们把 1 也算进去,打表发现每个值的出现次数满足如下函数关系:
cnt(i)=⎩⎨⎧i−12n−i+1i≤n+1i>n+1
于是设 pre[i]=∑j=1i(j−1)jk,suf[i]=∑j=i2n(2n−j+1)jk,sum[i]=∑j=1ijk,则:
f(i)=pre[i+1]+(suf[i+2]−suf[2i+1])−(2n−2i)(sum[2i]−sum[i+1])
可以看成以 i+1 为分界,前面的用 pre[i+1],后面先用 suf[i+2]−suf[2i+1] 算出在 [i+2,2i] 中的和,因为 suf[i] 以 2n 为参考,还包含 [i+1,2n] 中的值,现在以 2i 为参考,所以会多算 (2n−2i)(sum[2i]−sum[i+1])。
方法 2:上述式子有更简单的表示方法,设 s1(n)=∑i=1nik,s2(n)=∑i=1ns1(i),则 f(n)=s2(2n)−2s2(n),证明省略,事实证明方法 1 更容易发现。
两种方法预处理的时间复杂度都是 Θ(n)。
求 g(n)
根据积性函数的判断性质:若 f(n),g(n) 均为积性函数,则下列函数也为积性函数:
h1(n)=f(n)g(n)h2(n)=d∣n∑f(dn)g(d)
分析 g(n)=∑d∣nμ2(d)μ(dn)d 的表达式:g(n) 是 μ2(n)n 和 μ(n) 的 Dirchlet 卷积,μ2(n)n 由三个函数 μ(n),μ(n),n 三个函数相乘得到,故 g(n) 为积性函数。
分析 g(n) 的性质,可以先从 g(pk) 的值入手,其中 p 为质数:
- k=0,则 g(pk)=g(1)=μ3(1)=1
- k=1,则 g(pk)=g(p)=μ2(1)μ(p)+μ2(p)μ(1)p=p−1
- k=2,则 g(pk)=g(p2)=μ2(1)μ(p2)+μ2(p)μ(p)p+μ2(p2)μ(1)=−p
- k≥3,根据抽屉原理,d 和 dpk 中至少有一个的 p 的指数大于 1,故 μ2(d) 和 μ(dpk) 至少有一个为 0,相乘后 g(pk)=0
所以在欧拉筛时根据 i⋅prime[j] 与 prime[j] 的关系进行计算即可。时间复杂度 Θ(n)。
求 idk(n)
再考虑求 idk(n),这是个完全积性函数,所以随便筛一下就可以了,比如在遇到质数 p 时用快速幂求一下 idk(p),其他时候就令 idk(i⋅prime[j])=idk(i)idk(prime[j]) 即可,时间复杂度 Θ(n+π(n)log2k)≈Θ(n)。
然后预处理一下 g(n)idk(n) 的前缀和,数论分块求解即可,总时间复杂度 O(n+n)。这种方法也可以通过 1≤n≤107 + 多组询问的加强版。
#include <iostream>
using namespace std;
constexpr int MAX_N = 5e6 + 10;
constexpr int MAX_SIZE = 1e7 + 10;
constexpr int MOD = 998244353;
int n;
long long k;
int prime[MAX_SIZE], tot;
bool notPrime[MAX_SIZE];
int prod[MAX_SIZE], f[MAX_N], g[MAX_SIZE], sum[MAX_N];
int fPre[MAX_SIZE], fSuf[MAX_SIZE], fSum[MAX_SIZE];
int ans;
int power(int x, long long y) {
int res = 1;
for (; y; y /= 2) {
if (y % 2)
res = 1ll * res * x % MOD;
x = 1ll * x * x % MOD;
}
return res;
}
void sieve(int n) {
n *= 2;
prod[1] = 1;
g[1] = 1;
for (int i = 2; i <= n; ++i) {
if (!notPrime[i]) {
prime[++tot] = i;
prod[i] = power(i, k);
g[i] = i - 1;
}
for (int j = 1; j <= tot && 1ll * i * prime[j] <= n; ++j) {
const int next = i * prime[j];
notPrime[next] = true;
prod[next] = 1ll * prod[i] * prod[prime[j]] % MOD;
if (i % prime[j]) {
g[next] = g[i] * (prime[j] - 1);
} else if ((i / prime[j]) % prime[j]) {
g[next] = -g[i / prime[j]] * prime[j];
break;
} else {
g[next] = 0;
break;
}
}
}
n /= 2;
for (int i = 1; i <= n; ++i) {
sum[i] = (sum[i - 1] + 1ll * prod[i] * g[i]) % MOD;
sum[i] = (sum[i] + MOD) % MOD;
}
}
void preprocess(int n) {
for (int i = 1; i <= 2 * n; ++i) {
fPre[i] = (fPre[i - 1] + 1ll * prod[i] * (i - 1)) % MOD;
fSum[i] = (fSum[i - 1] + prod[i]) % MOD;
}
for (int i = 2 * n; i >= 1; --i)
fSuf[i] = (fSuf[i + 1] + 1ll * prod[i] * (2 * n - i + 1)) % MOD;
for (int i = 1; i <= n; ++i) {
int mid = i + 1, res = 0;
res = ((fPre[mid] + fSuf[mid + 1]) % MOD - fSuf[2 * i + 1]) % MOD;
res = (res - 1ll * (2 * n - 2 * i) * (fSum[2 * i] - fSum[mid])) % MOD;
res = (res + MOD) % MOD;
f[i] = res;
}
}
int main() {
ios::sync_with_stdio(false);
cin >> n >> k;
sieve(n);
preprocess(n);
for (int l = 1, r; l <= n; l = r + 1) {
r = n / (n / l);
ans = (ans + 1ll * (sum[r] - sum[l - 1] + MOD) * f[n / l]) % MOD;
}
cout << ans << endl;
return 0;
}