莫比乌斯反演是组合数学中很重要的内容,可以用于解决很多组合数学的问题。
求满足 a≤x≤b,c≤y≤d 的所有数对 (x,y) 中, gcd(x,y)=k 的数对的个数。
一个测试点总共有 T 组数据。
1≤T≤50000,1≤a≤b≤50000,1≤c≤d≤50000,1≤k≤50000
我们设 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 的取值有 ⌊nk⌋ 种, b 的取值有 ⌊mk⌋ 种,那么显然 g(k)=⌊nk⌋⌊mk⌋ 。
也就是 g(k) 其实可以在 O(1) 的时间复杂度内计算。
那我们考虑如何通过 g 函数倒推 f 函数,由上列式子,显然有
考虑这样一类函数(不要问我和上面有什么关系)
经过不断地猜想和验证,我们可以发现 f 貌似满足这样的关系式:
证明:
对于数 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 ,等于 μ(x)μ(y) 。
μ(y)=0 的情况类似。
μ(x)μ(y)≠0 时,即 μ(x)=(−1)n , μ(y)=(−1)m 。 xy 肯定是同样的这些质数的乘积,因此 μ(xy)=(−1)n+m ,等于 μ(x)μ(y) 。
综上所述, μ(x)μ(y)=μ(xy) 。
证明:
要证明的式子为
那么我们回到式子 f(n)=∑d|ng(d)μ(nd) 。之前我们说这是个猜想,那这个东西怎么证明呢?
套用 g 函数定义式得,证明上式即要证
在实际运用中,很少直接用到莫比乌斯反演的定义形式。
我们通常遇到的问题是以这种形式出现的:
可以证明,当 g 为积性函数时, f 为积性函数。
这里只证明莫比乌斯反演基本形式的 f ,其变式类似。
证明:
令 gcd(A,B)=1 , g 为某积性函数。
既然 μ 是积性函数,那么是否可以通过线性筛法线性地筛出 μ 的值呢?
显然:
∙ 对于 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 满足 g(k)=∑⌊nk⌋i=1f(i×k)=⌊nk⌋×⌊mk⌋ ,显然我们可以套用莫比乌斯反演的变式 g(d)=∑⌊nd⌋i=1f(di)⇒f(d)=∑⌊nd⌋i=1g(di)μ(i) 。
也就是
我们考虑一个问题: ∀d∈N∗,d∈[1,n] 所有 ⌊nd⌋ 的取值有多少种?
∙∀d∈N∗,d∈[1,⌊n√⌋] ,显然 d 总共有 n√ 种取值方法,那么 ⌊nd⌋ 肯定最多有 n√ 种取值方法。
∙∀d∈N∗,d∈(⌊n√⌋,n] , ⌊nd⌋ 是小于等于 n√ 的,那么 ⌊nd⌋ 肯定最多有 n√ 种取值方法。
综上所述, ∀d∈N∗,d∈[1,n] 所有 ⌊nd⌋ 的取值最多就有 2n√ 种。
现在思路就很显然了,我们对于 i 分块,使得每个块内 ⌊nki⌋ 、 ⌊mki⌋ 的值都相等。由上面证明得使用最优策略下,这样的块最多有 2(n√+m−−√) 个。
每个块内的 μ 值可以用前缀和处理,然后乘上相同的系数。
总的时间复杂度为 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√ 的时间复杂度内解决。最后跟我高呼:“分块大法好,分块大法好,分块大法好!”