算法简介
莫比乌斯反演是组合数学中很重要的内容,可以用于解决很多组合数学的问题。
算法分析
先给个题目
[HAOI2011][BZOJ2301]Problem b
求满足
a≤x≤b,c≤y≤d
的所有数对
(x,y)
中,
gcd(x,y)=k
的数对的个数。
一个测试点总共有
T
组数据。
题目分析
简化问题
我们设
f(k)(n,m)
为满足
1≤x≤n,1≤y≤m
中所有数对
(x,y)
中,
gcd(x,y)=k
的数对个数。
n,m
在函数计算前已经给定,且
n≤m
。
那么答案显然就是
f(k)(c,d)−f(k)(a−1,d)−f(k)(b,c−1)+f(k)(a−1,c−1)
。
但是这样的
f(k)
很难直接在较优时间复杂度内求出,怎么办呢?
化难为易
我们发现
f(k)
很难直接求,那能不能用一个容易计算的东西来推出
f(k)
呢?
定义
g(k)=∑⌊nk⌋i=1f(ik)
,即
k|gcd(x,y)
的数对
(x,y)
个数。
既然
k|gcd(x,y)
,那么一定满足
x=ak,y=bk(a,b∈N+)
。
a
的取值有
也就是
g(k)
其实可以在
O(1)
的时间复杂度内计算。
那我们考虑如何通过
g
函数倒推
那么
难道我们用这条式子从大到小推出 f(k) 吗,其实这样对复杂度并没有什么太大的优化。
那 f 函数和
寻找联系
考虑这样一类函数(不要问我和上面有什么关系)
我们列举 g(x) 与 f(x) 之间的关系如下:
g(1)=f(1)
g(2)=f(1)+f(2)
g(3)=f(1)+f(3)
g(4)=f(1)+f(2)+f(4)
g(5)=f(1)+f(5)
g(6)=f(1)+f(2)+f(3)+f(6)
g(7)=f(1)+f(7)
g(8)=f(1)+f(2)+f(4)+f(8)
g(9)=f(1)+f(3)+f(9)
g(10)=f(1)+f(2)+f(5)+f(10)
尝试使用 g(x) 写出 f(x) :
f(1)=g(1)
f(2)=g(2)−g(1)
f(3)=g(3)−g(1)
f(4)=g(4)−g(2)
f(5)=g(5)−g(1)
f(6)=g(6)−g(3)−g(2)+g(1)
f(7)=g(7)−g(1)
f(8)=g(8)−g(4)
f(9)=g(9)−g(3)
f(10)=g(10)−g(5)−g(2)+g(1)
可以发现, f(x) 的等式中所有 g(y) 都满足 y|x 。
那么是否有 f(n)=∑d|ng(d) 呢?显然不是,有的 g(d)(d|n) 没有出现在等式中,有的甚至是以相反数的形式出现。
那么我们可以猜测每个 g(d) 都乘上了一个系数。那么这个系数到底和什么有关呢? d ?
莫比乌斯反演
基本知识
经过不断地猜想和验证,我们可以发现
f
貌似满足这样的关系式:
其中 μ 是一个函数,满足:
即当 x 能分解为若干互不相等的质数的乘积时(注意,不是质数的乘幂的乘积),设这样的质数有
μ 函数的性质
性质1.1: μ 是积性函数
证明:
对于数
x,y
满足
gcd(x,y)=1
,那么设
x=∏ni=1piai
,
y=∏mi=1qibi
,显然有
∀1≤i≤n,1≤j≤m
,满足
gcd(pi,qj)=1
。
若
μ(x)=0
,那么存在至少一个
1≤i≤n
使得
ai>1
,那
xy
分解后肯定存在一个因数为某质数的乘幂,因此
μ(xy)
一定为
0
,等于
μ(y)=0
的情况类似。
μ(x)μ(y)≠0
时,即
μ(x)=(−1)n
,
μ(y)=(−1)m
。
xy
肯定是同样的这些质数的乘积,因此
μ(xy)=(−1)n+m
,等于
μ(x)μ(y)
。
综上所述,
μ(x)μ(y)=μ(xy)
。
性质1.2:对
n
的因子的μ 值求和,当
n=1
时,和为
1
,否则为0
证明:
要证明的式子为
设 n 能分解为
考虑这些
研究过杨辉三角及二项式系数的人应该知道
在这里,我们令 a=−1 , b=1 ,则有
得证。
反演证明以及部分性质
反演证明
那么我们回到式子
f(n)=∑d|ng(d)μ(nd)
。之前我们说这是个猜想,那这个东西怎么证明呢?
套用
g
函数定义式得,证明上式即要证
更换主体,令 T=dd1 则有要证
莫比乌斯反演的常用形式
在实际运用中,很少直接用到莫比乌斯反演的定义形式。
我们通常遇到的问题是以这种形式出现的:
由此可以推到
证明和上面的证明类似:
积性函数
可以证明,当
g
为积性函数时,
这里只证明莫比乌斯反演基本形式的
f
,其变式类似。
证明:
令
线性求出 μ 函数
既然
μ
是积性函数,那么是否可以通过线性筛法线性地筛出
μ
的值呢?
显然:
∙
对于
p∈P
,有
μ(p)=−1
∙
对于
p∈P,a∈N∗,a>1
,有
μ(pa)=0
∙
对于
p,q∈N∗,gcd(p,q)=1
,有
μ(pq)=μ(p)μ(q)
∙
特殊地,
μ(1)=1
那么用线性筛法求
μ
的过程就显然了。
mu[1]=1;
pri[0]=0;
mark[1]=true;
for (int i=2;i<=N;i++)
{
if (!mark[i])
{
pri[++pri[0]]=i;
mu[i]=-1;
}
for (int j=1;j<=pri[0];j++)
{
if ((long long)i*pri[j]>N)
break;
mark[i*pri[j]]=true;
if (!(i%pri[j]))
{
mu[i*pri[j]]=0;
break;
}
mu[i*pri[j]]=-mu[i];
}
}
回到题目
莫比乌斯反演
题目中我们要求的目标函数为
f(k)
,且有函数
g
满足
也就是
筛 μ 函数可以在 O(n) 的时间复杂度内解决,但是求 f(k) 时总不能枚举 ⌊nk⌋ 次吧,这样对时间复杂度并没有什么帮助,有什么好的办法求解呢?
分块大法好
我们考虑一个问题:
∀d∈N∗,d∈[1,n]
所有
⌊nd⌋
的取值有多少种?
∙∀d∈N∗,d∈[1,⌊n√⌋]
,显然
d
总共有
∙∀d∈N∗,d∈(⌊n√⌋,n]
,
⌊nd⌋
是小于等于
n√
的,那么
⌊nd⌋
肯定最多有
n√
种取值方法。
综上所述,
∀d∈N∗,d∈[1,n]
所有
⌊nd⌋
的取值最多就有
2n√
种。
现在思路就很显然了,我们对于
i
分块,使得每个块内
每个块内的
μ
值可以用前缀和处理,然后乘上相同的系数。
总的时间复杂度为
O(n+n√)
,具体实现细节请看代码实现。
代码实现
#include <iostream>
#include <cstdio>
#include <cctype>
#include <cmath>
using namespace std;
int read()
{
int x=0,f=1;
char ch=getchar();
while (!isdigit(ch))
{
if (ch=='-')
f=-1;
ch=getchar();
}
while (isdigit(ch))
{
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
const int N=50000;
int mu[N+1],pri[N+1],sum[N+1];
int k,a,b,c,d,T;
bool mark[N+1];
void preparation()
{
pri[0]=0;
mark[1]=true;
mu[1]=1;
for (int i=2;i<=N;i++)
{
if (!mark[i])
{
pri[++pri[0]]=i;
mu[i]=-1;
}
for (int j=1;j<=pri[0];j++)
{
if ((long long)i*pri[j]>N)
break;
mark[i*pri[j]]=true;
mu[i*pri[j]]=-mu[i];
if (!(i%pri[j]))
{
mu[i*pri[j]]=0;
break;
}
}
}
sum[1]=mu[1];
for (int i=2;i<=N;i++)
sum[i]=sum[i-1]+mu[i];
}
int calc(int n,int m)
{
if (n>m)
n^=m^=n^=m;
int i=1,j,ret=0;
while (i<=n/k)
{
int j1=ceil(n/(k*(n/(k*i)))),j2=ceil(m/(k*(m/(k*i))));
j=min(j1,j2);
ret+=(sum[j]-sum[i-1])*(n/(k*i))*(m/(k*i));
i=j+1;
}
return ret;
}
int main()
{
preparation();
freopen("b.in","r",stdin);
freopen("b.out","w",stdout);
T=read();
while (T--)
{
a=read(),b=read(),c=read(),d=read(),k=read();
int ans1,ans2,ans3,ans4,ans;
ans1=calc(b,d);
ans2=calc(b,c-1);
ans3=calc(a-1,d);
ans4=calc(a-1,c-1);
ans=ans1-ans2-ans3+ans4;
printf("%d\n",ans);
}
fclose(stdin);
fclose(stdout);
return 0;
}
算法总结
回顾上面的题目,可以归纳出一般的莫比乌斯反演问题的解题步骤(是一般的,难题会比较坑)。
∙
首先推导公式:根据题目大意,用准确的数学语言表述出求解的步骤(不要怕大暴力),这一步是解题的基础。
∙
其次等价变换:有了求解式子之后,要想办法通过莫比乌斯反演的形式进行变形和优化。莫比乌斯反演的题目往往不会给你一个十分简单的求解式,最终式子的推出需要我们灵活运用换元、内外层求和对调等技巧,这其中认清我们要将哪一条式子作为主体,哪些式子作为系数,从而向所想的方向变化。
∙
再次线性筛法:通常式子中的系数可以通过强大的线性筛法求出,这其中往往会运用到目标函数的积性。我们要注意分析和发现函数的性质。
∙
最后分块大法:一般的莫比乌斯反演最后的式子都会出现形如
⌊nd⌋
的形式,这样就可以通过分块大法在
n√
的时间复杂度内解决。最后跟我高呼:“分块大法好,分块大法好,分块大法好!”