莫比乌斯反演学习小记

算法简介

莫比乌斯反演是组合数学中很重要的内容,可以用于解决很多组合数学的问题。

算法分析

先给个题目

[HAOI2011][BZOJ2301]Problem b

求满足 axb,cyd 的所有数对 (x,y) 中, gcd(x,y)=k 的数对的个数。
一个测试点总共有 T 组数据。
1T500001ab500001cd500001k50000

题目分析

简化问题

我们设 f(k)(n,m) 为满足 1xn,1ym 中所有数对 (x,y) 中, gcd(x,y)=k 的数对个数。 n,m 在函数计算前已经给定,且 nm
那么答案显然就是 f(k)(c,d)f(k)(a1,d)f(k)(b,c1)+f(k)(a1,c1)
但是这样的 f(k) 很难直接在较优时间复杂度内求出,怎么办呢?

化难为易

我们发现 f(k) 很难直接求,那能不能用一个容易计算的东西来推出 f(k) 呢?
定义 g(k)=nki=1f(ik) ,即 k|gcd(x,y) 的数对 (x,y) 个数。
既然 k|gcd(x,y) ,那么一定满足 x=ak,y=bk(a,bN+) a 的取值有 nk 种, b 的取值有 mk 种,那么显然 g(k)=nkmk
也就是 g(k) 其实可以在 O(1) 的时间复杂度内计算。
那我们考虑如何通过 g 函数倒推 f 函数,由上列式子,显然有

i=1nkf(ik)=nkmk

那么
f(k)=nkmki=2nkf(ik)

难道我们用这条式子从大到小推出 f(k) 吗,其实这样对复杂度并没有什么太大的优化。
f 函数和 g 函数之间还存在着什么联系呢?

寻找联系

考虑这样一类函数(不要问我和上面有什么关系)

g(n)=d|nf(d)

我们列举 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 nd ?请读者先自行研究。

莫比乌斯反演

基本知识

经过不断地猜想和验证,我们可以发现 f 貌似满足这样的关系式:

f(n)=d|ng(d)μ(nd)

其中 μ 是一个函数,满足:
μ(x)={(1)k,0,x=ki=1pi,piPotherwise

即当 x 能分解为若干互不相等的质数的乘积时(注意,不是质数的乘幂的乘积),设这样的质数有 k 个,那么 μ(x)=(1)k ,否则 μ(x)=0

μ 函数的性质

性质1.1: μ 是积性函数

证明:
对于数 x,y 满足 gcd(x,y)=1 ,那么设 x=ni=1piai y=mi=1qibi ,显然有 1in,1jm ,满足 gcd(pi,qj)=1
μ(x)=0 ,那么存在至少一个 1in 使得 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)

性质1.2:对 n 的因子的 μ 值求和,当 n=1 时,和为 1 ,否则为 0

证明:
要证明的式子为

d|nμ(d)={0,1,n=1n>1

n 能分解为 n=ki=1piai 。那么对求和答案有贡献的显然是由若干互不相等质数乘起来的 d
考虑这些 d ,那么显然可得
d|nμ(d)=i=0k(ki)(1)i

研究过杨辉三角及二项式系数的人应该知道
(a+b)k=i=0k(ki)aibki

在这里,我们令 a=1 b=1 ,则有
d|nμ(d)=i=0k(ki)(1)i=i=0k(ki)(1)i1ki=(1+1)k={0,1,k=0k>0

得证。

反演证明以及部分性质

反演证明

那么我们回到式子 f(n)=d|ng(d)μ(nd) 。之前我们说这是个猜想,那这个东西怎么证明呢?
套用 g 函数定义式得,证明上式即要证

f(n)=d|nd1|df(d1)μ(dd1)

更换主体,令 T=dd1 则有要证
f(n)=d|nd1|df(d1)μ(dd1)=d1|nf(d1)T|nd1μ(T)=f(n)1.2f(d1)1nd11d1=n0

莫比乌斯反演的常用形式

在实际运用中,很少直接用到莫比乌斯反演的定义形式。
我们通常遇到的问题是以这种形式出现的:

g(d)=i=1ndf(di)

由此可以推到
f(d)=i=1ndg(di)μ(i)

证明和上面的证明类似:
i=1ndg(di)μ(i)=i=1ndj=1ndif(dij)μ(j)=T=1ndf(Td)j|Tμ(j)=f(d)

积性函数

可以证明,当 g 为积性函数时, f 为积性函数。
这里只证明莫比乌斯反演基本形式的 f ,其变式类似。
证明:
gcd(A,B)=1 g 为某积性函数。

f(A)f(B)=d1|Ag(d1)μ(Ad1)d2|Bg(d2)μ(Bd2)=T|ABd1|Tgcd(d1,B)=1g(d1)g(Td1)μ(Ad1)μ(Bd1T)=T|ABd1|Tgcd(d1,B)=1g(T)μ(ABT)(gμ)=T|ABg(T)μ(ABT)(d1)=f(AB)

线性求出 μ 函数

既然 μ 是积性函数,那么是否可以通过线性筛法线性地筛出 μ 的值呢?
显然:
对于 pP ,有 μ(p)=1
对于 pPaN,a>1 ,有 μ(pa)=0
对于 p,qN,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)=nki=1f(i×k)=nk×mk ,显然我们可以套用莫比乌斯反演的变式 g(d)=ndi=1f(di)f(d)=ndi=1g(di)μ(i)
也就是

f(k)=i=1nkg(ki)μ(i)=i=1nknkimkiμ(i)

μ 函数可以在 O(n) 的时间复杂度内解决,但是求 f(k) 时总不能枚举 nk 次吧,这样对时间复杂度并没有什么帮助,有什么好的办法求解呢?

分块大法好

我们考虑一个问题: dN,d[1,n] 所有 nd 的取值有多少种?
dN,d[1,n] ,显然 d 总共有 n 种取值方法,那么 nd 肯定最多有 n 种取值方法。
dN,d(n,n] nd 是小于等于 n 的,那么 nd 肯定最多有 n 种取值方法。
综上所述, dN,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 的时间复杂度内解决。最后跟我高呼:“分块大法好,分块大法好,分块大法好!”

你可能感兴趣的:(算法,数论,OI,莫比乌斯反演)