Typesetting math: 44%

「笔记」莫比乌斯反演

Updated on 2020.8.6
巨幅更新,对积性函数和狄利克雷卷积部分进行重构。
新增对一类特殊求和式的讲解。

Updated on 2020.9.9
添加了几个例题。


前置知识

小碎骨

艾佛森括号 [P]={1If P is true0Otherwise[P]={1If P is true0Otherwise
此处 PP 是一个可真可假的命题。

引理1

a,b,cZ,abc=abca,b,cZ,abc=⎢ ⎢ ⎢ ⎢abc⎥ ⎥ ⎥ ⎥

证明

ab=ab+r(0r<1)ab=ab+r(0r<1)

abc=ab×1c=1c×(ab+r)=abc+rc=abcabc=ab×1c=1c×(ab+r)=⎢ ⎢ ⎢ ⎢abc+rc⎥ ⎥ ⎥ ⎥=⎢ ⎢ ⎢ ⎢abc⎥ ⎥ ⎥ ⎥

数论分块

内容独立了出来,详细内容见 数论分块 - Luckyblock

对于一类含有nini的求和式 (nn 为常数),由于nini单调不增,故存在多个区间[l,r][l,r], 使得ni=nj(i,j[l,r])ni=nj(i,j[l,r]) 成立。

对于任意一个ii,最大的满足上式的 j=nnij=⎢ ⎢ ⎢ ⎢nni⎥ ⎥ ⎥ ⎥


积性函数

定义

gcd(x,y)=1gcd(x,y)=1f(xy)=f(x)f(y)f(xy)=f(x)f(y),则 f(n)f(n) 为积性函数。

性质

f(x)f(x)g(x)g(x)均为积性函数,则以下函数也为积性函数:

h(x)=f(xp)h(x)=fp(x)h(x)=f(x)g(x)h(x)=dxf(d)g(xd)h(x)=f(xp)h(x)=fp(x)h(x)=f(x)g(x)h(x)=dxf(d)g(xd)

常见积性函数

  • 单位函数 e(n)=[n=1]e(n)=[n=1]
  • 幂函数 Idk(n)=nkIdk(n)=nkid1(n)id1(n) 通常简记为id(n)id(n)
  • 常数函数 1(n)=11(n)=1
  • 因数个数 d(n)=dn1d(n)=dn1
  • 除数函数 σk(n)=dndkσk(n)=dndk
    k=1k=1 时为因数和函数,通常简记为 σ(n)σ(n)
    k=0k=0 时为因数个数函数 σ0(n)σ0(n)
  • 欧拉函数 φ(n)=ni=1[gcd(i,n)=1]φ(n)=ni=1[gcd(i,n)=1]
  • 莫比乌斯函数 μ(n)={1n=10n 含有平方因子(1)kk n 的本质不同质因子个数μ(n)=1n=10n (1)kk n 

不是很懂上面写的什么玩意?
不用深究,有个印象继续往下看就好。


莫比乌斯函数

定义

μμ 为莫比乌斯函数,定义为

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

解释

n=ki=1pciin=ki=1pcii,其中pipi为质因子,ci1ci1

  1. n=1n=1时,μ(n)=1μ(n)=1
  2. n1n1时 ,
    • i[1,k],ci>1i[1,k],ci>1 时,μ(n)=0μ(n)=0
      当某质因子出现次数大于11时,μ(n)=0μ(n)=0
    • i[1,k],ci=1i[1,k],ci=1 时,μ(n)=(1)kμ(n)=(1)k
      当每个质因子只出现一次时,即n=ki=1pin=ki=1pi{pi}{pi}中元素唯一。
      μ(n)=(1)kμ(n)=(1)k,此处kk为质因子的种类数。

性质

莫比乌斯函数是积性函数,且具有以下性质

dnμ(d)=[n=1]dnμ(d)=[n=1]

证明,设 n=ki=1pcii,n=ki=1pin=ki=1pcii,n=ki=1pi

  • 根据莫比乌斯函数定义,则有:dnμ(d)=dnμ(d)dnμ(d)=dnμ(d)
  • nn 的某因子 dd,有 μ(d)=(1)iμ(d)=(1)i,则它由 ii 个 本质不同的质因子组成。
    由于质因子总数为 kk,满足上式的因子数为 CikCik
  • 对于原求和式,转为枚举 μ(d)μ(d) 的值。
    dnμ(d)=ki=0Cik×(1)i=ki=0Cik×(1)i×1kidnμ(d)=ki=0Cik×(1)i=ki=0Cik×(1)i×1ki
    根据二项式定理,上式 =(1+(1))k=(1+(1))k
    易知该式在 k=0k=0,即 n=1n=1 时为 11,否则为 00

反演常用结论

一个反演常用结论:

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

证明 1:
n=gcd(i,j)n=gcd(i,j),则右=dnμ(d)=[n=1]=[gcd(i,j)=1]==dnμ(d)=[n=1]=[gcd(i,j)=1]= 左。

证明 2:
暴力展开:[gcd(i,j)=1]=e(gcd(i,j))=dgcd(i,j)μ(d)[gcd(i,j)=1]=e(gcd(i,j))=dgcd(i,j)μ(d)

线性筛求莫比乌斯函数

μμ 为积性函数,因此可以线性筛莫比乌斯函数。

复制复制
int pnum, mu[kMaxn], p[kMaxn];
bool vis[kMaxn];
void Euler(int lim_) {
vis[1] = true, mu[1] = 1ll;
for (int i = 2; i <= lim_; ++ i) {
if (! vis[i]) {
p[++ pnum] = i;
mu[i] = - 1;
}
for (int j = 1; j <= pnum && i * p[j] <= lim_; ++ j) {
vis[i * p[j]] = true;
if (i % p[j] == 0) { //勿忘平方因子
mu[i * p[j]] = 0;
break;
}
mu[i * p[j]] = - mu[i];
}
}
}

狄利克雷(Dirichlet)卷积

建议阅读 算法学习笔记(35): 狄利克雷卷积 By: Pecco

定义两个数论函数 f,gf,g 的狄利克雷卷积为

(fg)(n)=dnf(d)g(nd)(fg)(n)=dnf(d)g(nd)

性质

  1. 显然满足 交换律,结合律,分配律。
    • fg=gffg=gf
    • (fg)h=f(gh)(fg)h=f(gh)
    • f(g+h)=fg+fhf(g+h)=fg+fh
  2. ee 为狄利克雷卷积的单位元,有(fe)(n)=f(n)(fe)(n)=f(n)
  3. f,gf,g 为积性函数,则 fgfg 为积性函数。

关于单位元 ee

有:

e=μ1=dnμ(d)e=μ1=dnμ(d)

证明

(fe)(n)=dnf(d)e(nd)=dnf(d)[nd=1](fe)(n)=dnf(d)e(nd)=dnf(d)[nd=1]

  • 对于[nd=1][nd=1],当且仅当 nd=1nd=1,即 d=nd=n 时为 11,否则为00
  • 则当 d=nd=n 时,f(d)[nd=1]=f(n)f(d)[nd=1]=f(n)
    dndn 时,f(d)[nd=1]=0f(d)[nd=1]=0

综上,(fe)(n)=dnf(d)[nd=1]=f(n)(fe)(n)=dnf(d)[nd=1]=f(n),满足单位元的性质。
e=μ1e=μ1 成立。

除数函数与幂函数

幂函数 Idk(n)=nkIdk(n)=nk
除数函数 σk(n)=dndkσk(n)=dndk

显然有:

(Idk1)(n)=dnIdk(d)=dndk=σk(n)(Idk1)(n)=dnIdk(d)=dndk=σk(n)

k=0k=0 时,Id0=1Id0=1σ0σ0 为因数个数函数,有:

(11)(n)=dn1=σ0(n)(11)(n)=dn1=σ0(n)

欧拉函数与恒等函数

φ1=Idφ=Idμφ1=Idφ=Idμ

对于一式,当 n=pmn=pm 时(pp 为质数),有:

(φ1)(pm)=dnφ(d)=φ(1)+mi=1φ(pi)=1+mi=1(pipi1)=pm(φ1)(pm)=dnφ(d)=φ(1)+mi=1φ(pi)=1+mi=1(pipi1)=pm

pipi 的因子有 pi1pi1 个,为 1pi11pi1,故 φ(pi)=pipi1φ(pi)=pipi1

(φ1)(n)(φ1)(n) 为积性函数,则对于任意正整数 nn,有:

(φ1)(n)=(φ1)(pm)=(φ1)(pm)=pm=n(φ1)(n)=(φ1)(pm)=(φ1)(pm)=pm=n

得证。

对于 2 式,在 1 式基础上两侧同时 μμ 即得。
左侧变为 φ1μ=φe=φφ1μ=φe=φ

计算

(fg)(n)=dnf(d)g(nd)(fg)(n)=dnf(d)g(nd)

考虑枚举 nn 的因子,将贡献累加即可。
显然可以使用埃氏筛筛出所有前缀狄利克雷卷积,复杂度 O(nklogn)O(nklogn),其中 kk 是计算函数值的复杂度。


莫比乌斯反演

反演是个啥?反演

公式

f(n),g(n)f(n),g(n)为两个数论函数。
如果有

f(n)=(g1)(n)=dng(d)f(n)=(g1)(n)=dng(d)

那么有

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

证明

方法一:运用卷积。

原问题为:已知 f=g1f=g1,证明 g=fμg=fμ
易知如下转化:fμ=g1μfμ=ge=gfμ=g1μfμ=ge=g

方法二:对原式进行数论变换。

  1. dng(d)dng(d) 替换f(nd)f(nd)

    dnμ(d)kndg(k)dnμ(d)kndg(k)

  2. 变换求和顺序。

    kng(k)dnkμ(d)kng(k)dnkμ(d)

  3. 依据 dnμ(d)=[n=1]dnμ(d)=[n=1],仅当 nk=1nk=1 时,dnkμ(d)=1dnkμ(d)=1,否则为 00
    此时k=nk=n,故上式等价于 kn[n=k]g(k)=g(n)kn[n=k]g(k)=g(n)

举例

狄利克雷(Dirichlet)卷积 部分可以知道一些积性函数的关系。
尝试对它们进行反演:

e=μ1μ=μe=μe=μ1μ=μe=μ

σk=Idk1Idk=μσkσk=Idk1Idk=μσk

Id=φ1φ=IdμId=φ1φ=Idμ


关于一类求和式

ni=1mj=1f(gcd(i,j))ni=1mj=1f(gcd(i,j))

一般套路

考虑构造出函数 gg,满足下列形式:

f(n)=g1=dng(d)f(n)=g1=dng(d)

则求和式变为:

ni=1mj=1dgcd(i,j)g(d)ni=1mj=1dgcd(i,j)g(d)

考虑算术基本定理,发现若满足 dgcd(i,j)dgcd(i,j),则 dididjdj 成立。
考虑 g(d)g(d) 在何时做出贡献,调整枚举顺序:

d=1g(d)ni=1[di]mj=1[dj]d=1g(d)ni=1[di]mj=1[dj]

ni=1[di]ni=1[di] 等价于 1n1ndd 的倍数的个数,则上式等价于:

d=1g(d)ndmdd=1g(d)ndmd

数论分块求解即可。

例 1

ni=1mj=1gcd(i,j)ni=1mj=1gcd(i,j)

发现此题的 ff 等价于 IdId,则上式等价于:

ni=1mj=1Id(gcd(i,j))=ni=1mj=1dgcd(i,j)φ(d)=d=1φ(d)ndmdni=1mj=1Id(gcd(i,j))=ni=1mj=1dgcd(i,j)φ(d)=d=1φ(d)ndmd

例 2

ni=1mj=1[gcd(i,j)=1]=ni=1mj=1e(gcd(i,j))=ni=1mj=1dgcd(i,j)μ(d)=d=1μ(d)ndmdni=1mj=1[gcd(i,j)=1]=ni=1mj=1e(gcd(i,j))=ni=1mj=1dgcd(i,j)μ(d)=d=1μ(d)ndmd

感悟

卷点什么东西,把 gg 卷出来。
gg 不一定是特殊意义的函数。


例题

[HAOI2011] Problem b

nn 组询问,每次给定参数 a,b,c,d,ka,b,c,d,k,求:

bx=ady=c[gcd(x,y)=k]bx=ady=c[gcd(x,y)=k]

1n,k,a,b,c,d5×1041n,k,a,b,c,d5×104ab,cdab,cd

f(n,m)=ni=1mj=1[gcd(i,j)=k]f(n,m)=ni=1mj=1[gcd(i,j)=k]
根据容斥原理,则原式等价于 f(b,d)f(a1,d)f(b,d1)+f(a1,d1)f(b,d)f(a1,d)f(b,d1)+f(a1,d1)
ff 变成了上述一类求和式的形式,考虑化简 ff

易知原式等价于

nki=1mkj=1[gcd(i,j)=1]nki=1mkj=1[gcd(i,j)=1]

代入反演常用结论 [gcd(i,j)=1]=dgcd(i,j)μ(d)[gcd(i,j)=1]=dgcd(i,j)μ(d),上式化为

nki=1mkj=1dgcd(i,j)μ(d)nki=1mkj=1dgcd(i,j)μ(d)

变换求和顺序,先枚举dgcd(i,j)dgcd(i,j),可得

d=1μ(d)nki=1[di]mkj=1[dj]d=1μ(d)nki=1[di]mkj=1[dj]

对于上式后半的解释:当dididjdj 时,dgcd(i,j)dgcd(i,j)

易知1nk1nkdd 的倍数有 nkd=nkd⎢ ⎢ ⎢ ⎢nkd⎥ ⎥ ⎥ ⎥=nkd 个(由引理 1 可知),原式变为

d=1μ(d)nkdmkdd=1μ(d)nkdmkd

预处理 μμ 后,显然可以数论分块求解,复杂度O(n+Tn)O(n+Tn)


代码

//知识点:莫比乌斯反演
/*
//By:Luckyblock
*/
#include <cstdio>
#include <cctype>
#include <algorithm>
#define ll long long
const int MARX = 6e4 + 10;
//=============================================================
int N, a, b, c, d, k;
int cnt, Mobius[MARX], Prime[MARX], Sum[MARX];
bool vis[MARX];
//=============================================================
inline int read()
{
int f = 1, w = 0; char ch = getchar();
for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = -1;
for(; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void GetMobius()
{
Mobius[1] = 1;
int MAX = MARX - 10;
for(int i = 2; i <= MAX; i ++)
{
if(! vis[i]) Mobius[i] = - 1, Prime[++ cnt] = i;
for(int j = 1; j <= cnt && i * Prime[j] <= MAX; j ++)
{
vis[i * Prime[j]] = true;
if(i % Prime[j] == 0) break;
Mobius[i * Prime[j]] = - Mobius[i];
}
}
for(int i = 1; i <= MAX; i ++) Sum[i] = Sum[i - 1] + Mobius[i];
}
ll Calc(int x, int y)
{
ll ans = 0ll; int max = std :: min(x, y);
for(int l = 1, r; l <= max; l = r + 1)
r = std :: min(x / (x / l), y / (y / l)),
ans += (1ll * x / (1ll * l * k)) * (1ll * y / (1ll * l * k)) * (Sum[r] - Sum[l - 1]);
return ans;
}
ll Solve()
{
a = read(), b = read(), c = read(), d = read(), k = read();
return Calc(b, d) - Calc(b, c - 1) - Calc(a - 1, d) + Calc(a - 1, c - 1);
}
//=============================================================
int main()
{
N = read(); GetMobius();
while(N --) printf("%lld\n", Solve());
return 0;
}

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

给定 n,mn,m , 求:

ni=1mj=1lcm(i,j)mod20101009ni=1mj=1lcm(i,j)mod20101009

1n,m1071n,m107

易知原式等价于:

ni=1mj=1ijgcd(i,j)ni=1mj=1ijgcd(i,j)

考虑枚举 gcd(i,j)gcd(i,j),设枚举量为 dd
d=gcd(i,j)d=gcd(i,j) 的充要条件是满足 d|i,d|jd|i,d|jgcd(id,jd)=1gcd(id,jd)=1,则原式等价于:

ni=1mj=1d=1ijd[d|i][d|j][gcd(id,jd)=1]ni=1mj=1d=1ijd[d|i][d|j][gcd(id,jd)=1]

先枚举 dd,则原式等价于:

d=1ni=1[di]mj=1[dj][gcd(id,jd=1)]ijdd=1ni=1[di]mj=1[dj][gcd(id,jd=1)]ijd

这个 dd 很烦人,把 i,ji,j 中的 dd 提出来,变为枚举 ididjdjd
消去 dididjdj 的限定条件,则原式等价于:

d=1ndi=1mdj=1[gcd(i,j)=1]id×jdd=d=1ndi=1mdj=1[gcd(i,j)=1]ijd=d=1dndi=1mdj=1[gcd(i,j)=1]ijd=1ndi=1mdj=1[gcd(i,j)=1]id×jdd=d=1ndi=1mdj=1[gcd(i,j)=1]ijd=d=1dndi=1mdj=1[gcd(i,j)=1]ij

单独考虑后半部分,设 f(x,y)=xi=1yj=1[gcd(i,j)=1]ijf(x,y)=xi=1yj=1[gcd(i,j)=1]ij
发现 f(x,y)f(x,y) 的形式与 [HAOI2011] Problem b 中的式子类似,代入 [gcd(i,j)=1]=dgcd(i,j)μ(d)[gcd(i,j)=1]=dgcd(i,j)μ(d) 套路一波:

f(x,y)=xi=1yj=1[gcd(i,j)=1]ij=xi=1yj=1dgcd(i,j)μ(d)ij=d=1μ(d)xi=1[di]yj=1[dj]ij=d=1μ(d)d2xdi=1ydj=1ijf(x,y)=xi=1yj=1[gcd(i,j)=1]ij=xi=1yj=1dgcd(i,j)μ(d)ij=d=1μ(d)xi=1[di]yj=1[dj]ij=d=1μ(d)d2xdi=1ydj=1ij

前半部分 d=1μ(d)d2d=1μ(d)d2,可以考虑筛出 μ(d)μ(d) 后求前缀和。
后半部分是等差数列乘等差数列的形式,设 g(p,q)=pi=1qj=1ijg(p,q)=pi=1qj=1ijgp,qgp,q 可以通过下式 O(1)O(1) 计算:

g(p,q)=pi=1iqj=1j=(1+p)×p2×(1+q)×q2g(p,q)=pi=1iqj=1j=(1+p)×p2×(1+q)×q2

则对于 f(x,y)f(x,y),有:

f(x,y)=d=1μ(d)d2g(xd,yd)f(x,y)=d=1μ(d)d2g(xd,yd)

数论分块求解即可。

再看回原式,原式等价于:

d=1df(nd,md)d=1df(nd,md)

又是一个可以数论分块求解的形式。
线性筛预处理后 数论分块套数论分块,复杂度 O(n+m)O(n+m),瓶颈是线性筛。


一些注意的点

处理时会出现求平方的运算,需要特别注意取模问题,ll 都会爆,被坑惨了。

在预处理前缀和的这个地方:

sum[i] = (sum[i - 1] + 1ll * i * i % kMod * (mu[i] + kMod)) % kMod; //仅令 mu + kMod

注意先对平方取模,在乘上 μμ,否则就会爆掉。
以及可以仅令 mu+modmu+mod

以及这个地方:

int g(int n_, int m_) {
return (1ll * n_ * (n_ + 1ll) / 2ll % kMod) * (1ll * m_ * (m_ + 1ll) / 2ll % kMod) % kMod;
}

平方计算,注意随时取模。

代码

//知识点:莫比乌斯反演
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstring>
#define ll long long
const ll kMod = 20101009;
const int kMaxn = 1e7 + 10;
//=============================================================
int pnum, p[kMaxn];
ll mu[kMaxn], sum[kMaxn];
bool vis[kMaxn];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Getmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Getmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
void Euler(int lim_) {
vis[1] = true, mu[1] = 1ll;
for (int i = 2; i <= lim_; ++ i) {
if (! vis[i]) {
p[++ pnum] = i;
mu[i] = - 1;
}
for (int j = 1; j <= pnum && i * p[j] <= lim_; ++ j) {
vis[i * p[j]] = true;
if (i % p[j] == 0) { //勿忘平方因子
mu[i * p[j]] = 0;
break;
}
mu[i * p[j]] = - mu[i];
}
}
sum[1] = 1ll;
for (int i = 1; i <= lim_; ++ i) {
sum[i] = (sum[i - 1] + 1ll * i * i % kMod * (mu[i] + kMod)) % kMod; //仅令 mu + kMod
}
}
int g(int n_, int m_) {
return (1ll * n_ * (n_ + 1ll) / 2ll % kMod) * (1ll * m_ * (m_ + 1ll) / 2ll % kMod) % kMod;
}
int f(int n_, int m_) {
int lim = std :: min(n_, m_), ret = 0;
for (int l = 1, r; l <= lim; l = r + 1) {
r = std :: min(n_ / (n_ / l), m_ / (m_ / l));
ret = (ret + 1ll * (sum[r] - sum[l - 1] + kMod) * g(n_ / l, m_ / l) % kMod) % kMod;
}
return ret;
}
int Sum(ll l, ll r) {
return (1ll * (r - l + 1ll) * (l + r) / 2ll) % kMod;
}
//=============================================================
int main() {
int n = read(), m = read();
int lim = std :: min(n, m), ans = 0;
Euler(lim);
for (int l = 1, r; l <= lim; l = r + 1) {
r = std :: min(n / (n / l), m / (m / l));
ans = (ans + 1ll * Sum(l, r) * f(n / l, m / l) % kMod) % kMod;
}
printf("%d", ans);
return 0;
}
/*
7718820 8445880
*/

SP5971 LCMSUM - LCM Sum

TT 次询问,每次询问给定 nn,求:

ni=1lcm(i,n)ni=1lcm(i,n)

1<T3×1051<T3×1051n1061n106

法一:无脑暴力

先拆 lcmlcm,原式等价于:

nni=1igcd(i,n)nni=1igcd(i,n)

套路的枚举 gcd(i,n)gcd(i,n),调换枚举顺序,原式等价于:

ni=1d|i[gcd(i,n)=d]id=ni=1d|i[gcd(id,nd)=1]id=nd|nni=1[d|i][gcd(id,nd)=1]idni=1d|i[gcd(i,n)=d]id=ni=1d|i[gcd(id,nd)=1]id=nd|nni=1[d|i][gcd(id,nd)=1]id

i,ni,n 中的 dd 提出来,变为枚举 idid,消去整除的条件,原式等价于:

nd|nndi=1[gcd(i,nd)=1]ind|nndi=1[gcd(i,nd)=1]i

代入 [gcd(i,j)=1]=dgcd(i,j)μ(d)[gcd(i,j)=1]=dgcd(i,j)μ(d),原式等价于:

nd|nndi=1it|gcd(i,nd)μ(t)

值得注意的是 t 的上界为 nddtn
调换枚举顺序,先枚举 t,原式等价于:

nd|nt|ndμ(t)ndi=1[t|i]i

套路地消去整除的条件,把 i 中的 t 提出来,原式等价于:

nd|nt|ndμ(t)tndti=1i

对于最后的一个求和项,设 g(x)=xi=1i=x(x+1)2,显然可以 O(1) 求解,原式等价于:

nd|nt|ndμ(t)tg(ndt)

考虑枚举 T=dt,显然 Tn
μ(t)td 无关,可以直接考虑枚举 t|T,原式等价于:

nnT=1g(nT)t|Tμ(t)t

前半块是一个数论分块的形式,可以 O(n) 求解。
考虑后半块,设 f(n)=d|nμ(d)d,发现它是一个积性函数,可以线性筛筛出,具体地:

f(n)={1nnprimesf(xp)p2nf(xp)f(p)p2n

其中 pn 的最小质因子。

此时已经可以线性筛 + 数论分块求解,复杂度 O(n+Tn),比较菜鸡,时限 500ms 过不了。
考虑筛出 f 后再用埃氏筛预处理 nT=1g(nT)f(T),输出时乘上 n,复杂度变为 O(nlog2n+n)


法二:

同样先拆 lcm,枚举 gcd(i,n),调换枚举顺序,原式等价于:

nni=1igcd(i,n)=ni=1d|i[gcd(i,n)=d]id=nd|nni=1[d|i][gcd(i,n)=d]id

i,n 中的 d 提出来,变为枚举 id,消去整除的条件,原式等价于:

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

调整枚举对象,上式等价于:

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

考虑 di=1[gcd(i,d)=1]i 的实际意义,表示 [1,d] 中与 d 互质的数的和。
d>1 时,与 d 互质的数总是成对存在,即若 gcd(i,d)=1 成立,则 gcd(di,d)=1 成立。
每对这样的数的和为 d,共有 φ(d)2 对这样的数。
则原式等价于:

nd|nφ(d)d2

可以直接预处理答案。
预处理时先线性筛出 φ,再埃氏筛枚举 i 的倍数,令它们的答案加上 φ(i)i2,最后输出时乘上 n
复杂度 O(nlog2n+T)


法二代码

//知识点:莫比乌斯反演
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define ll long long
const int kMaxn = 1e6;
//=============================================================
ll phi[kMaxn + 10], ans[kMaxn + 10];
int pnum, p[kMaxn + 10];
bool flag[kMaxn + 10];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void GetMax(int &fir, int sec) {
if (sec > fir) fir = sec;
}
void GetMin(int &fir, int sec) {
if (sec < fir) fir = sec;
}
void GetPrime() {
phi[1] = 1, flag[1] = true; //注意初始化
for (int i = 2; i <= kMaxn; ++ i) {
if (! flag[i]) {
p[++ pnum] = i;
phi[i] = i - 1ll;
}
for (int j = 1; j <= pnum && i * p[j] <= kMaxn; ++ j) {
flag[i * p[j]] = true;
if (i % p[j]) {
phi[i * p[j]] = phi[i] * phi[p[j]];
} else {
phi[i * p[j]] = phi[i] * p[j];
break;
}
}
}
for (int i = 1; i <= kMaxn; ++ i) {
for (int j = 1; i * j <= kMaxn; ++ j) {
ans[i * j] += (i == 1 ? 1 : 1ll * phi[i] * i / 2);
}
}
}
//=============================================================
int main() {
GetPrime();
int T = read();
while (T --) {
int n = read();
printf("%lld\n", 1ll * ans[n] * n);
}
return 0;
}

[SDOI2015]约数个数和

T 次询问,每次询问给定 n,m
定义 >d(i)i 的约数个数,求:

ni=1mj=1d(ij)

1<T,n5×104

一个结论:

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

证明

先考虑 i=paj=pb(pprimes) 的情况,有:

d(pa+b)=x|pay|pb[gcd(x,y)=1]

对于等式左侧,pa+b 的约数个数为 a+b+1
对于等式右侧,在保证 gcd(x,y)=1 成立的情况下,有贡献的数对 (x,y) 只能是下列三种形式:

  • x>0,y0xa 种取值方法。
  • x=0,y>0yb 种取值方法。
  • x=0,y=0

则等式右侧贡献次数为 a+b+1 次,等于 pa+b 的约数个数。
则当 i=paj=pb(pprimes) 时等式成立。

又不同质因子间相互独立,上述情况可拓展到一般的情况。


d(i,j) 进行化简,代入 [gcd(i,j)=1]=dgcd(i,j)μ(d),原式等价于:

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

调换枚举顺序,先枚举 d,原式等价于:

d=1[d|i][d|j]μ(d)x|i[d|x]y|j[d|y]

把各项中的 d 提出来,消去整除的条件,原式等价于:

d=1[d|i][d|j]μ(d)x|idy|jd1=d=1[d|i][d|j]μ(d)d(id)d(jd)


d(ij) 代回原式,原式等价于:

ni=1mj=1d=1[d|i][d|j]μ(d)d(id)d(jd)

调换枚举顺序,先枚举 d,原式等价于:

d=1μ(d)ni=1[d|i]mj=1[d|j]d(id)d(jd)

i,j 中的 d 提出来,变为枚举 id,jd,消去整除的条件,原式等价于:

d=1μ(d)ndi=1d(i)mdj=1d(j)

考虑预处理 S(x)=xi=1d(i),则原式等价于:

d=1μ(d)S(nd)S(md)

线性筛预处理 μ,d,数论分块求解即可,复杂度 O(n+Tn)


代码

//知识点:莫比乌斯反演
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstring>
#define ll long long
const int kMaxn = 5e4 + 10;
//=============================================================
int pnum, p[kMaxn];
ll mu[kMaxn], num[kMaxn], d[kMaxn]; //num 为最小质因子的次数
ll summu[kMaxn], sumd[kMaxn];
bool vis[kMaxn];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Getmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Getmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
void Euler(int lim_) {
vis[1] = true;
mu[1] = d[1] = 1ll;
for (int i = 2; i <= lim_; ++ i) {
if (! vis[i]) {
p[++ pnum] = i;
mu[i] = - 1;
num[i] = 1;
d[i] = 2;
}
for (int j = 1; j <= pnum && i * p[j] <= lim_; ++ j) {
vis[i * p[j]] = true;
if (i % p[j] == 0) { //勿忘平方因子
mu[i * p[j]] = 0;
num[i * p[j]] = num[i] + 1;
d[i * p[j]] = 1ll * d[i] / num[i * p[j]] * (num[i * p[j]] + 1ll);
break;
}
mu[i * p[j]] = - mu[i];
num[i * p[j]] = 1;
d[i * p[j]] = 2ll * d[i]; //
}
}
for (int i = 1; i <= lim_; ++ i) {
summu[i] = summu[i - 1] + mu[i];
sumd[i] = sumd[i - 1] + d[i];
}
}
//=============================================================
int main() {
Euler(kMaxn - 10);
int T = read();
while (T --) {
int n = read(), m = read(), lim = std :: min(n, m);
ll ans = 0ll;
for (int l = 1, r; l <= lim; l = r + 1) {
r = std :: min(n / (n / l), m / (m / l));
ans += 1ll * (summu[r] - summu[l - 1]) * sumd[n / l] * sumd[m / l]; //
}
printf("%lld\n", ans);
}
return 0;
}
/*
in
1
32 43
*/
/*
out
15420
*/

P3768 简单的数学题

给定参数 np,求:

(ni=1nj=1ijgcd(i,j))modp

n10105×108p1.1×109pprimes
时限 4s。

无脑套路暴力。

考虑先枚举 gcd(i,j),原式等价于:

d=1dni=1nj=1[gcd(i,j)=d]ij=d=1dni=1[d|i]nj=1[d|j][gcd(id,jd)=1]ij

提出 d,变为枚举 idjd,消去整除的条件,原式等价于:

d=1dndi=1ndj=1[gcd(i,j)=1]idjd=d=1d3ndi=1ndj=1[gcd(i,j)=1]ij

代入 [gcd(i,j)=1]=dgcd(i,j)μ(d),原式等价于:

d=1d3ndi=1ndj=1ijt|gcd(i,j)μ(t)

值得注意的是 t 的上界为 nddtn
调换枚举顺序,先枚举 t,原式等价于:

d=1d3t=1μ(t)ndi=1[t|i]ndj=1[t|j]ij

和上面一样,提出 t,套路地消去整除的条件,原式等价于:

d=1d3t=1μ(t)t2ndti=1ndtj=1ij

发现后面两个求和是等差数列乘等差数列的形式。
g(p,q)=pi=1qj=1ijgp,q 可以通过下式 O(1) 计算:

g(p,q)=pi=1iqj=1j=(1+p)×p2×(1+q)×q2

代入原式,原式等价于:

d=1d3t=1μ(t)t2g(ndt,ndt)

考虑枚举 T=dt,显然 Tn
再考虑枚举 d|T,即可得到 t=Td,原式等价于:

nT=1g(nT,nT)d|Td3μ(Td)(Td)2=nT=1T2g(nT,nT)d|Tdμ(Td)

对于后面这一坨,用 d|Tdμ(Td)=Idμ(T)=φ(T) 反演,则原式等价于:

nT=1T2φ(T)g(nT,nT)

后半块可以数论分块,考虑前半块。
发现前半段即为 Id2(T)φ(T),又是前缀和形式,考虑杜教筛。

有:

f(n)=Id2φ(n)

考虑找到一个函数 g,构造函数 h=fg 使其便于求值,有:

h(n)=d|nd2φ(d)g(nd)

看到同时存在 dnd,考虑把 d2 消去。
g=Id2,有:

h(n)=d|nd2φ(d)(nd)2=n2d|nφ(d)=n2φ1(n)

φ1=Id,则有:

h(n)=n3

找到了合适的 g,套杜教筛的公式。

g(1)S(n)=ni=1h(i)ni=2g(i)S(ni)S(n)=ni=1i3ni=2i2S(ni)

前一项是自然数的立方和,有 ni=1i3=(n(n+1)2)2。证明详见:自然数前n项平方和、立方和公式及证明 - 百度文库
后一项直接等差数列求和 + 数论分块求解即可。


「SDOI2017」数字表格

fi 表示斐波那契数列的第 i 项。
T 组数据,每次给定参数 n,m,求:

ni=1mj=1fgcd(i,j)(mod109+7)

1T1031n,m106
5S,256MB。

以下钦定 nm
大力化式子,先套路地枚举 gcd(i,j),用初中知识把两个 化到指数位置,原式等于:

nd=1ni=1mj=1fd[gcd(i,j)=d]=nd=1f(ni=1mj=1[gcd(i,j)=d])d

分母套路一波,有:

ni=1mj=1[gcd(i,j)=d]=ndi=1mdj=1[gcd(i,j)=1]=ndi=1mdj=1kgcd(i,j)μ(k)=k=1μ(k)nkdmkd

代回原式,原式等于:

nd=1f(k=1μ(k)nkdmkd)d=nd=1(fk=1μ(k)d)nkdmkd

考虑再暴力拆一波,原式等于:

nd=1(fk=1μ(k)d)nkdmkd=nd=1(ndk=1fμ(k)nkdmkdd)

做不动了,但发现变量仅有 k,d,kd,考虑更换枚举对象改为枚举 t=kdd,则原式等于:

nt=1(nd|tfμ(td)d)ntmt

枚举对象变成了约数形式。从后面的式子推前面的式子是比较显然的,可以认为这种枚举 t=kd 的形式是一种逆向操作。

设:

g(t)=nd|tfμ(td)d

g(t) 可以用类似埃氏筛的方法 O(nlog2n) 地预处理出来。再把 g 代回原式,原式等于:

nt=1g(t)ntmt

可以考虑预处理 g(t) 的前缀积,数论分块枚举指数求解即可。

总时间复杂度 O(nlog2n+Tn),轻微卡常可以过。

//知识点:莫比乌斯反演
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const LL mod = 1e9 + 7;
const int kN = 1e6;
//=============================================================
LL n, m, ans;
int p_num, p[kN + 10];
bool vis[kN + 10];
LL mu[kN + 10], f[kN + 10], g[kN + 10], prod[kN + 10];
LL invf[kN + 10], invp[kN];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) {
w = (w << 3) + (w << 1) + (ch ^ '0');
}
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
LL QPow(LL x_, LL y_) {
x_ %= mod;
LL ret = 1;
for (; y_; y_ >>= 1ll) {
if (y_ & 1) ret = ret * x_ % mod;
x_ = x_ * x_ % mod;
}
return ret;
}
void Euler() {
vis[1] = true, mu[1] = 1;
for (int i = 2; i <= kN; ++ i) {
if (! vis[i]) {
p[++ p_num] = i;
mu[i] = -1;
}
for (int j = 1; j <= p_num && i * p[j] <= kN; ++ j) {
vis[i * p[j]] = true;
if (i % p[j] == 0) {
mu[i * p[j]] = 0;
break;
}
mu[i * p[j]] = -mu[i];
}
}
}
void Prepare() {
g[1] = g[2] = 1;
f[1] = f[2] = 1;
invf[1] = invf[2] = 1;
for (int i = 3; i <= kN; ++ i) {
g[i] = 1;
f[i] = (f[i - 1] + f[i - 2]) % mod;
invf[i] = QPow(f[i], mod - 2);
}
Euler();
for (int d = 1; d <= kN; ++ d) {
for (int j = 1; d * j <= kN; ++ j) {
if (mu[j] == 1) {
g[d * j] = g[d * j] * f[d] % mod;
} else if (mu[j] == -1) {
g[d * j] = g[d * j] * invf[d] % mod;
}
}
}
invp[0] = prod[0] = 1;
for (int i = 1; i <= kN; ++ i) {
prod[i] = prod[i - 1] * g[i] % mod;
invp[i] = QPow(prod[i], mod - 2);
}
}
//=============================================================
int main() {
Prepare();
int T = read();
while (T -- ){
n = read(), m = read(), ans = 1;
if (n < m) std::swap(n, m);
for (LL l = 1, r = 1; l <= m; l = r + 1) {
r = std::min(n / (n / l), m / (m / l));
ans = (ans * QPow(prod[r] * invp[l - 1] % mod, 1ll * (n / l) * (m / l))) % mod;
}
printf("%lld\n", ans);
}
return 0;
}

P5518 [MtOI2019]幽灵乐团 / 莫比乌斯反演基础练习题

给定参数 p,有 T 组数据,每次给定参数 A,B,C,求:

Ai=1Bj=1Ck=1(lcm(i,j)gcd(i,k))f(type)

其中 f(type) 的取值如下:

f(type)={1type=0i×j×ktype=1gcd(i,j,k)type=2

1A,B,C105107p1.05×109pPT=70
2.5S,128MB。

先化下原式,原式等于:

Ai=1Bj=1Ck=1(i×jgcd(i,j)×gcd(i,k))f(type)

发现每一项仅与两个变量有关,设:

f1(a,b,c)=ai=1bj=1ck=1if(type)f2(a,b,c)=ai=1bj=1ck=1gcd(i,j)f(type)

发现 可以随意交换,则原式等价于:

f1(a,b,c)×f1(b,a,c)f2(a,b,c)×f2(a,c,b)

考虑在 type 取值不同时,如何快速求得 f1f2
一共有 6 个需要推导的式子,不妨就叫它们 16 面了(


type = 0

f1(a,b,c)=ai=1bj=1ck=1if2(a,b,c)=ai=1bj=1ck=1gcd(i,j)

对于 1 面,显然有:

ai=1bj=1ck=1i=(ai=1i)b×c

预处理阶乘 + 快速幂即可,单次计算时间复杂度 O(logn)


再考虑 2 面,套路地枚举 gcd,显然有:

ai=1bj=1ck=1gcd(i,j)=(ai=1bj=1gcd(i,j))c=(d=1d(ai=1bj=1[gcd(i,j)=d]))c

指数是个套路,可以看这里:P3455 [POI2007]ZAP-Queries。于是有:

ai=1bj=1[gcd(i,j)=d]=adi=1bdj=1[gcd(i,j)=1]=adi=1bdj=1kgcd(i,j)μ(k)=k=1μ(k)akdbkd

代回原式,略做处理,则原式等于:

(d=1d(k=1μ(k)akdbkd))c=(d=1(dk=1μ(k))akdbkd)c=d=1(ndk=1d(μ(k)akdbkd))c

「SDOI2017」数字表格 一样,考虑枚举 t=kdd,则原式等于:

nt=1((d|tdμ(td))atbt)c

设:

g0(t)=d|tdμ(td)

线性筛预处理 μ 后,g0(t) 可以用埃氏筛预处理,时间复杂度 O(nlogn)。再代回原式,原式等于:

at=1(g0(t)atbt)c

预处理 g0(t) 的前缀积和前缀积的逆元,复杂度 O(nlogn)
数论分块 + 快速幂计算即可,单次时间复杂度 O(nlogn)


type = 1

f1(a,b,c)=ai=1bj=1ck=1ii×j×kf2(a,b,c)=ai=1bj=1ck=1gcd(i,j)i×j×k

考虑 3 面,把 k 扔到指数位置,有:

ai=1bj=1ck=1ii×j×k=ai=1bj=1i(i×j×ck=1k)

再把 j 也扔到指数位置,引入 sum(n)=ni=1i=n(n+1)2,原式等于:

(ai=1ii)sum(b)×sum(c)

预处理 ii 的前缀积,复杂度 O(nlogn)
指数可以 O(1) 算出,然后快速幂,单次时间复杂度 O(logn)

根据费马小定理,指数需要对 p1 取模。注意 p1 不是质数,计算 sum 时不能用逆元,但乘不爆 LL,直接算就行。


再考虑 4 面,发现 kgcd 无关,则同样把 k 扔到指数位置,则有:

ai=1bj=1ck=1gcd(i,j)i×j×k=(ai=1bj=1gcd(i,j)i×j)sum(c)

套路地枚举 gcd,原式等于:

(d=1d(ai=1bj=1i×j[gcd(i,j)=d]))sum(c)

大力化简指数,有:

ai=1bj=1i×j[gcd(i,j)=d]=d2adi=1bdj=1i×j[gcd(i,j)=1=d2adi=1ibdj=1jt|gcd(i,j)μ(t)=d2adi=1ibdj=1jk|gcd(i,j)μ(k)=d2k=1μ(k)adi=1i[k|i]bdj=1j[k|j]=d2k=1k2μ(k)akdi=1ibkdj=1j=d2k=1k2μ(k)sum(akd)sum(bkd)

指数化不动了,代回原式,原式等于:

(d=1d(d2k=1k2μ(k)sum(akd)sum(bkd)))sum(c)

同 2 面的情况,先展开一下,再枚举 t=kdd,原式等于:

(d=1(ndk=1d(d2k2μ(k)))(sum(akd)sum(bkd)))sum(c)=t=1((d|td(d2(td)2μ(td)))sum(at)sum(bt))sum(c)=t=1((d|td(t2μ(td)))sum(at)sum(bt))sum(c)

与二面相同,设:

g1(t)=d|td(t2μ(td))

g1(t) 可以用埃氏筛套快速幂筛出,时间复杂度 O(nlog2n)。再代回原式,原式等于:

t=1(g1(t)sum(at)sum(bt))sum(c)

同样预处理 g1(t) 的前缀积及其逆元,时间复杂度 O(nlogn)
整除分块 + 快速幂即可,单次时间复杂度 O(nlogn)

注意指数的取模。


type = 2

f1(a,b,c)=ai=1bj=1ck=1igcd(i,j,k)f2(a,b,c)=ai=1bj=1ck=1gcd(i,j)gcd(i,j,k)

考虑 5 面,手段同上,大力反演化简一波,再调换枚举对象,则有:

ai=1bj=1ck=1igcd(i,j,k)=d=1ai=1i(bj=1ck=1[gcd(i,j,k)=d])=d=1ai=1i(bdj=1cdk=1[gcd(id,j,k)=1])=d=1adi=1(id)(dbdj=1cdk=1[gcd(i,j,k)=1])=d=1adi=1(id)(dbdj=1cdk=1x|gcd(i,j,k)μ(x))=d=1adi=1(id)(dx=1μ(x)[x|i]bdj=1[x|j]cdk=1[x|k])=d=1x=1adi=1(id)(d×μ(x)[x|i]bdj=1[x|j]cdk=1[x|k])=d=1x=1axdi=1(ixd)(d×μ(x)bxdcxd)=t=1d|Tati=1(it)(d×μ(td)btct)=t=1d|T(tatati=1i)d×μ(td)btct

引入 fac(n)=ni=1i,再根据枚举对象调整一下指数,原式等于:

t=1d|t(tat×fac(at))(d×μ(td)btct)=t=1(d|t(tat×fac(at))d×μ(td))btct=t=1((tat×fac(at))d|td×μ(td))btct

指数中出现了一个经典的狄利克雷卷积的形式,对其进行反演。
(Idμ)(n)=φ(n) 代入原式,原式等于:

t=1(tat×fac(at))φ(t)btct=t=1(tφ(t)atbtct×fac(at)φ(t)btct)

预处理 tφ(t) 的前缀积及逆元,阶乘的前缀积及阶乘逆元,(modp1) 下的 φ(t) 的前缀和(指数
),时间复杂度 O(nlogn)
同样整除分块 + 快速幂即可,单次时间复杂度 O(nlogn)


然后是最掉 sans 的 6 面。有 gcd(i,j,k)=gcd(gcd(i,j),k),考虑先枚举 gcd(i,j),然后套路化式子,则有:

ai=1bj=1ck=1gcd(i,j)gcd(i,j,k)=d=1ai=1bj=1ck=1[gcd(i,j)=d]dgcd(d,k)=d=1(d(ai=1bj=1[gcd(i,j)=d]))ck=1gcd(d,k)

先考虑最外面的指数,这也是个套路,可以参考 一个例子。用 Id=φ1 反演,显然有:

ck=1gcd(d,k)=ck=1x|gcd(d,k)φ(x)=x=1φ(x)[x|d]ck=1[x|k]=x|dφ(x)cx

再考虑里面的指数,发现这式子在 2 面已经推了一遍了,于是直接拿过来用,有:

ai=1bj=1[gcd(i,j)=d]=y=1μ(y)aydbyd

将化简后的两个指数代入原式,原式等于:

d=1(d(y=1μ(y)aydbyd))x|dφ(x)cx=d=1(y=1d(μ(y)aydbyd))x|dφ(x)cx

与 2、4 面同样套路地,考虑枚举 t=ydd,再略作调整,原式等于:

d=1(y=1d(μ(y)aydbyd))x|dφ(x)cx=t=1d|td(μ(td)atbtx|dφ(x)cx)=t=1(d|td(μ(td)x|dφ(x)cx))atbt=t=1(d|tx|dd(μ(td)φ(x)cx))atbt

发现要同时枚举 dx,化不动了。
从题解里学到一个比较神的技巧,考虑把 d 拆成 xdx 分别计算贡献再相乘,即分别计算下两式:

t=1(d|tx|dx(μ(td)φ(x)cx))atbtt=1(d|tx|d(dx)(μ(td)φ(x)cx))atbt


先考虑 x 的情况,首先把枚举 x 调整到最外层。设 lim=max(a,b,c),则原式等于:

x=1limt=1[x|t](d|t[x|d]x(μ(td)φ(x)cx))atbt=x=1limxt=1(d|tx(μ(txdx)φ(x)cx))atxbtx=x=1limxt=1d|tx(φ(x)cxatxbtxμ(td))

t 挪到指数位置,原式等于:

x=1x(limxt=1d|tφ(x)cxatxbtxμ(td))=x=1x(φ(x)cxlimxt=1atxbtxd|tμ(td))

指数中又出现了一个经典的狄利克雷卷积的形式,对其进行反演。
(μ1)(n)=ϵ(n)=[n=1] 代入原式,原式等于:

x=1x(φ(x)cxlimxt=1atxbtx[t=1])=x=1x(φ(x)axbxcx)

得到了一个非常优美的式子,而且发现这个式子是 5 面最终答案的一部分。同 5 面的做法,直接整除分块即可。


再考虑 dx 的情况,同上先把枚举 x 放到最外层,并调整一下指数,则原式等于:

x=1limt=1[x|t](d|t[x|d](dx)(μ(td)φ(x)cx))atbt=x=1limxt=1(d|tx[x|d](dx)(μ(txd)φ(x)cx))atxbtx=x=1(limxt=1(d|tx[x|d](dx)μ(txd))atxbtx)φ(x)cx

考虑枚举 dx,替换原来的 d,注意一下这里的倍数关系。原式等于:

x=1(limxt=1(d|tdμ(td))atxbtx)φ(x)cx

发现最内层的式子 d|tdμ(td),即为二面处理过的 g0(t)。直接代入,原式等于:

x=1(limxt=1g0(t)atxbtx)φ(x)cx

一个小结论,证明可以看 这里

a,b,cZ,abc=abc

则原式等于:

x=1(limxt=1g0(t)axtbxt)φ(x)cx

于是可以先对外层整除分块,再对内层整除分块。

然后就做完了,哈哈哈。


一些实现上的小技巧:

  • 逆元能预处理就预处理。
  • 注意对指数取模,模数为 p1
//知识点:莫比乌斯反演
/*
By:Luckyblock
用了比较清晰易懂的变量名,阅读体验应该会比较好。
vsc 的自动补全真是太好啦!
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
using std::min;
using std::max;
#define LL long long
const int Lim = 1e5;
const int kN = 1e5 + 10;
//=============================================================
LL A, B, C, mod, ans;
int T, p_num, p[kN];
bool vis[kN];
LL mu[kN], phi[kN], fac[kN], g[2][kN];
LL sumphi[kN], prodt_phi[kN], prodi_i[kN], prodg[2][kN];
LL inv[kN], inv_fac[kN], inv_prodt_phi[kN], inv_prodg[2][kN];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) {
w = (w << 3) + (w << 1) + (ch ^ '0');
}
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
LL QPow(LL x_, LL y_) {
x_ %= mod;
y_ %= mod - 1;
LL ret = 1;
for (; y_; y_ >>= 1ll) {
if (y_ & 1) ret = ret * x_ % mod;
x_ = x_ * x_ % mod;
}
return ret;
}
LL Inv(LL x_) {
return QPow(x_, mod - 2);
}
LL Sum(LL n_) {
return ((n_ * (n_ + 1ll)) / 2ll) % (mod - 1);
}
void Euler() {
vis[1] = true, mu[1] = phi[1] = 1; //初值
for (int i = 2; i <= Lim; ++ i) {
if (! vis[i]) {
p[++ p_num] = i;
mu[i] = -1;
phi[i] = i - 1;
}
for (int j = 1; j <= p_num && i * p[j] <= Lim; ++ j) {
vis[i * p[j]] = true;
if (i % p[j] == 0) {
mu[i * p[j]] = 0;
phi[i * p[j]] = phi[i] * p[j];
break;
}
mu[i * p[j]] = -mu[i];
phi[i * p[j]] = phi[i] * (p[j] - 1);
}
}
}
void Prepare() {
Euler();
inv[1] = fac[0] = prodt_phi[0] = prodi_i[0] = 1;
for (int i = 1; i <= Lim; ++ i) {
g[0][i] = g[1][i] = 1;
fac[i] = 1ll * fac[i - 1] * i % mod;
sumphi[i] = (sumphi[i - 1] + phi[i]) % (mod - 1);
prodi_i[i] = prodi_i[i - 1] * QPow(i, i) % mod;
if (i > 1) inv[i] = (mod - mod / i) * inv[mod % i] % mod;
prodt_phi[i] = prodt_phi[i - 1] * QPow(i, phi[i]) % mod;
inv_prodt_phi[i] = Inv(prodt_phi[i]);
}
for (int d = 1; d <= Lim; ++ d) {
for (int j = 1; d * j <= Lim; ++ j) {
int t = d * j;
if (mu[j] == 1) {
g[0][t] = g[0][t] * d % mod;
g[1][t] = g[1][t] * QPow(1ll * d, 1ll * t * t) % mod;
} else if (mu[j] == -1) {
g[0][t] = g[0][t] * inv[d] % mod;
g[1][t] = g[1][t] * Inv(QPow(1ll * d, 1ll * t * t)) % mod;
}
}
}
inv_prodg[0][0] = prodg[0][0] = 1;
inv_prodg[1][0] = prodg[1][0] = 1;
inv_prodt_phi[0] = 1;
for (int i = 1; i <= Lim; ++ i) {
for (int j = 0; j <= 1; ++ j) {
prodg[j][i] = prodg[j][i - 1] * g[j][i] % mod;
inv_prodg[j][i] = Inv(prodg[j][i]);
}
}
}
LL f1(LL a_, LL b_, LL c_, int type) {
if (! type) return QPow(fac[a_], b_ * c_);
if (type == 1) return QPow(prodi_i[a_], Sum(b_) * Sum(c_));
LL ret = 1, lim = min(min(a_, b_), c_);
for (LL l = 1, r = 1; l <= lim; l = r + 1) {
r = min(min(a_ / (a_ / l), b_ / (b_ / l)), c_ / (c_ / l));
ret = ret * QPow(prodt_phi[r] * inv_prodt_phi[l - 1], (a_ / l) * (b_ / l) % (mod - 1) * (c_ / l)) % mod;
ret = ret * QPow(fac[a_ / l], (sumphi[r] - sumphi[l - 1] + mod - 1) % (mod - 1) * (b_ / l) % (mod - 1) * (c_ / l)) % mod;
}
return ret;
}
LL f2_2(LL a_, LL b_) {
LL ret = 1;
for (LL l = 1, r = 1; l <= min(a_, b_); l = r + 1) {
r = min(a_ / (a_ / l), b_ / (b_ / l));
ret = ret * QPow(prodg[0][r] * inv_prodg[0][l - 1], 1ll * (a_ / l) * (b_ / l)) % mod;
}
return ret;
}
LL f2(LL a_, LL b_, LL c_, int type) {
LL ret = 1;
if (! type) {
for (LL l = 1, r = 1; l <= min(a_, b_); l = r + 1) {
r = min(a_ / (a_ / l), b_ / (b_ / l));
LL val = QPow(prodg[0][r] * inv_prodg[0][l - 1], 1ll * (a_ / l) * (b_ / l));
ret = (ret * QPow(val, c_)) % mod;
}
} else if (type == 1) {
for (LL l = 1, r = 1; l <= min(a_, b_); l = r + 1) {
r = min(a_ / (a_ / l), b_ / (b_ / l));
LL val = QPow(prodg[1][r] * inv_prodg[1][l - 1], Sum(a_ / l) * Sum(b_ / l));
ret = (ret * QPow(val, Sum(c_))) % mod;
}
} else {
LL lim = min(min(a_, b_), c_);
for (LL l = 1, r = 1; l <= lim; l = r + 1) {
r = min(min(a_ / (a_ / l), b_ / (b_ / l)), c_ / (c_ / l));
ret = ret * QPow(f2_2(a_ / l, b_ / l), (sumphi[r] - sumphi[l - 1] + mod - 1) % (mod - 1) * (c_ / l)) % mod;
ret = ret * QPow(prodt_phi[r] * inv_prodt_phi[l - 1], (a_ / l) * (b_ / l) % (mod - 1) * (c_ / l)) % mod;
}
}
return ret;
}
//=============================================================
int main() {
T = read(), mod = read();
Prepare();
while (T -- ) {
A = read(), B = read(), C = read();
for (int i = 0; i <= 2; ++ i) {
ans = f1(A, B, C, i) * f1(B, A, C, i) % mod;
ans = ans * Inv(f2(A, B, C, i)) % mod * Inv(f2(A, C, B, i)) % mod;
printf("%lld ", ans);
}
printf("\n");
}
return 0;
}

写在最后

参考资料:
Oi-Wiki-莫比乌斯反演
算法学习笔记(35): 狄利克雷卷积 By: Pecco
题解 SP5971 【LCMSUM - LCM Sum】 - BJpers2 的博客
题解 SP5971 【LCMSUM - LCM Sum】 - Venus 的博客
题解 P3327 【[SDOI2015]约数个数和】 - suncongbo 的博客

把 Oi-Wiki 上的内容进行了复制 整理扩充。
我是个没有感情的复读机(大雾)

posted @   Luckyblock  阅读(2178)  评论(15编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示