莫比乌斯反演学习小记

本文详细介绍了莫比乌斯反演的概念及其在解决组合数学问题中的应用。通过一个具体的算法题目,逐步展示了如何利用莫比乌斯反演简化问题,并通过线性筛法和分块技巧高效求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

算法简介

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


算法分析

先给个题目

[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)都乘上了一个系数。那么这个系数到底和什么有关呢?dnd?请读者先自行研究。

莫比乌斯反演

基本知识

经过不断地猜想和验证,我们可以发现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=1piaiy=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)mxy肯定是同样的这些质数的乘积,因此μ(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=1b=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)=1g为某积性函数。

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分块,使得每个块内nkimki的值都相等。由上面证明得使用最优策略下,这样的块最多有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的时间复杂度内解决。最后跟我高呼:“分块大法好,分块大法好,分块大法好!”

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值