zoukankan      html  css  js  c++  java
  • 笔记 莫比乌斯反演

    反演

    反演的作用基本上就是将一个bool表达式转化成一个和式,从而减小程序的时间复杂度。

    欧拉反演

    欧拉反演是莫比乌斯反演的一种特例,通常用于解决gcd的求和问题,它的基本式子是

    [n=sum_{d|n}phi(d) ]

    下面简单证明一下,我们枚举(n)的每个因子(d),显然每个因子都有可能成为(n)(1-n)中的数的gcd,并且每个gcd都是,即(n=sum_{d|n}sum_{i=1}^{n}[gcd(i,n)==d]),其中([gcd(i,n)==d])这个是一个bool表达式,当且仅当([gcd(i,n)==d])时,该式子返回1,其余时候返回0。

    进一步化简得到

    [n=sum_{d|n}sum_{i=1}^{n}[gcd(frac{i}d,frac{n}d)==1] ]

    [n=sum_{d|n}sum_{i=1}^{frac{n}d}[gcd(i,frac{n}d)==1] ]

    [n=sum_{d|n}phi(frac{n}d) ]

    因为在枚举约数的时候,对于一个约数(d),总会有一个约数(frac{n}d)与之对应(可能相等),即所有的约数都是对称的,所以原式可以继续化简为

    [n=sum_{d|n}phi(d) ]

    证毕。

    有什么用呢?
    来看一个十分眼熟的式子。

    (sum_{i=1}^ngcd(i,n))的值。

    相信一些大佬会秒算出答案,即(sum_{d|n}d imesphi(frac{n}d))

    不如换一个角度推导,根据上述中的(n=sum_{d|n}phi(d))来化简一下。

    [sum_{i=1}^ngcd(i,n) ]

    [=sum_{i=1}^nsum_{d|gcd(i,n)}phi(d) ]

    [=sum_{i=1}^nsum_{d|i}sum_{d|n}phi(d) ]

    [=sum_{d|n}sum_{i=1}^nsum_{d|i}phi(d) ]

    显然每个(d)对答案的贡献只有(frac{n}d)个,好吧并不显然,举个例子,假设(d=1),那么(d)能整除这全部(n)个数,答案就会增加(n imesphi(1)),类似的,对于每个(d),答案都会增加(frac{n}d imesphi(d)),所以上式化简为

    [sum_{d|n}frac{n}dphi(d) ]

    因为约数是对称的,所以它和上边那个一眼的式子是相等的。

    虽然到这里反演仍旧没有发挥什么很大的作用,但是我们不妨继续魔改上边的式子,这次——

    (sum_{i=1}^nsum_{j=1}^ngcd(i,j))的值。这个总不能一眼看出答案吧

    所以我们继续反演

    [sum_{i=1}^nsum_{j=1}^ngcd(i,j) ]

    [=sum_{i=1}^nsum_{j=1}^nsum_{d|gcd(i,j)}phi(d) ]

    [=sum_{i=1}^nsum_{j=1}^nsum_{d|i}sum_{d|j}phi(d) ]

    直到这一步和上边的推导还是相似的,我不会告诉你我是复制的

    但是我们发现它不能像上边的式子继续化简了,因为(i,j)都是不确定的量,如果直接枚举,时间复杂度没有任何变化,仍旧是(O(N^2)),思考一下(i,j)共同有的是什么,没错是(d)

    于是可以枚举(d)

    对于每个约数(d),它所能造成的贡献还是(frac{n}d),不过由于这次没有条件(d|n),所以需要稍微变化一点,手模一下发现它成了(lfloorfrac{n}d floor),但在代码上边还是(n/d),上式继续化简为

    [sum_{d=1}^nlfloorfrac{n}d floorlfloorfrac{n}d floorphi(d) ]

    但是代码里要写成这样

    (n/d)*(n/d)*phi[d];

    而不是

    n/d*n/d*phi[d];

    因为必须要先向下取整再进行运算。

    也许这样还有别的奇奇怪怪的方法可以算出来,于是继续改变上述式子。

    (sum_{i=1}^nsum_{j=1}^mgcd(i,j))的值

    这个莫比乌斯反演也可以做,不过好像没有必要,仍旧套上边欧拉反演的式子,得到原式为

    [sum_{d=1}^{min(n,m)}lfloorfrac{n}d floorlfloorfrac{m}d floorphi(d) ]

    以上就是欧拉反演了,虽然有些时候用起来还可以但是大多时候有局限性,所以建议学习莫比乌斯反演

    莫比乌斯反演

    好了,那如果问题继续发展,改成——

    (sum_{i=1}^nsum_{j=1}^ngcd(i,j)==k)的值呢?

    但这样好像就没有办法使用欧拉反演了,于是我们考虑使用其它办法。

    首先引入一个函数(mu),它的定义为

    [mu(x)=left{ egin{array}{rcl} 1 & & {x==1}\ (-1)^k & & {x==p_1p_2…p_k}\ 0 & & {otherwise}\ end{array} ight. ]

    其实这就是所谓的莫比乌斯函数,深刻理解一下它的含义,特殊的,当(x==1)的时候,它的值为1,否则当(x)中所含的单个质因子数目不超过1时,它的值为-1的质因子数目次方,其余情况为0。

    假设在有两个定义在复数域上的函数(f,g)满足

    [f(x)=sum_{d|x}g(d) ]

    那么有

    [g(x)=sum_{d|x}mu(d)f(frac{x}d) ]

    至于证明,个人感觉使用Dirichlet卷积会比较好证明一点。

    定义两个数论函数(f,g)的Dirichlet卷积为
    ((f*g)(n)=sum_{d|n}f(d)g(frac{n}d))

    然后再介绍一些基本的数论函数

    (varepsilon(x))当且仅当(x==1)时返回1

    (d(x))返回(x)的约数个数

    (sigma(x))返回(x)的约数和

    (varphi(x))这个返回啥感觉可以不说

    (id(x))返回(x)

    (I(x))返回1

    由卷积的定义可知,原问题可以化为
    已知

    [f=g*I ]

    求证

    [g=mu*f ]

    这里我们用一个很常用的性质,

    [mu*I=varepsilon ]

    [mu*I=[n==1] ]

    我们先来证明一下,

    [mu*I=sum_{d|n}mu(d) ]

    也就是相当于求(n)的因子的莫比乌斯函数值的和。

    因为只有在一个数的单个质因子不超过1时,才会返回不为0的值,所以只需要考虑(d)中的质因子。

    假设(d)中共含有(k)个质因子,那么原式可以化为

    [1-C_k^1+C_k^2-C_k^3………+(-1)^kC_k^k ]

    于是你就会发现一个很神奇的东西,这个就是…二项式定理

    所以

    [mu*I=(1-1)^k ]

    当且仅当(k==0)时,原式为1,易得此时(n==1)

    证毕

    所以,对

    [f=g*I ]

    两边同时卷上(mu),得到

    [mu*f=g*I*mu ]

    然后

    Dirichlet 卷积满足交换律和结合律。

    证明 百度百科说的

    [mu*f=g*(I*mu) ]

    [g=f*mu ]

    Dirichlet 卷积还有很多性质,比如

    [varphi=id*mu ]

    由这个可以得到什么呢?

    两边同时卷上(I),会得到

    [varphi*I=id*mu*I=id*(mu*I) ]

    [varphi*I=id ]

    把它卷出来就会发现,这不就是欧拉反演的式子吗!

    那我们回到正题,求(sum_{i=1}^nsum_{j=1}^ngcd(i,j)==k)

    [f(k)=sum_{i=1}^nsum_{j=1}^ngcd(i,j)==k ]

    [F(d)=sum_{d|k}f(k)=lfloorfrac{n}d floorlfloorfrac{n}d floor ]

    那么反演一下,得到

    [f(k)=sum_{d|k}mu(frac{k}d)F(k) ]

    [f(k)=sum_{d|k}mu(frac{k}d)lfloorfrac{n}d floorlfloorfrac{n}d floor ]

    (T=frac{k}d)

    [f(k)=sum_{T=1}^{frac{n}k}mu(T)lfloorfrac{n}{kT} floorlfloorfrac{n}{kT} floor ]

    这么推导十分满足莫比乌斯反演的式子,但好像两个函数比较难设出来,所以可以采取一个强大某些时候可能推不出来的办法。

    举个例子

    [sum_{i=1}^nsum_{j=1}^ngcd(i,j)==k ]

    [sum_{i=1}^{lfloorfrac{n}{k} floor}sum_{j=1}^{lfloorfrac{n}{k} floor}gcd(i,j)==1 ]

    上边我们已经证明了

    [sum_{d|n}mu(d)=[n==1] ]

    (gcd)替换(n)得到

    [sum_{i=1}^{lfloorfrac{n}{k} floor}sum_{j=1}^{lfloorfrac{n}{k} floor}sum_{d|gcd(i,j)}mu(d) ]

    [sum_{i=1}^{lfloorfrac{n}{k} floor}sum_{j=1}^{lfloorfrac{n}{k} floor}sum_{d|i}sum_{d|j}mu(d) ]

    换一下位置

    [sum_{i=1}^{lfloorfrac{n}{k} floor}sum_{d|i}sum_{j=1}^{lfloorfrac{n}{k} floor}sum_{d|j}mu(d) ]

    仍旧像欧拉反演那样,枚举每一个(d),分别计算贡献。

    [sum_{d=1}^{lfloorfrac{n}{k} floor}lfloorfrac{n}{kd} floorlfloorfrac{n}{kd} floormu(d) ]

    推出来的式子是一样的。

    但是这样的时间复杂度可能还是(O(n))的,在面临很多询问的时候仍旧会挂,于是考虑继续优化,这时候需要用到整除分块,有一部分数,他们(lfloorfrac{n}{kd} floor)的值是一样的,并且对于值为(lfloorfrac{n}{i} floor)的块,它的右端点为(lfloorfrac{n}{lfloorfrac{n}{i} floor} floor),我们可以先线性筛出(mu)的前缀和,然后对于这些值一样的数一起计算,这样时间复杂度就是(O(sqrt n))的了。

  • 相关阅读:
    CentOS7.2中安装MongoDB
    django 面试题
    python pandas库——pivot使用心得
    归并排序
    python实现归并排序,归并排序的详细分析
    二分法查找
    二叉树的遍历
    RabbitMQ(python实现)学习之一:简单两点传输“Hello World”的实现
    邻接表存储图,DFS遍历图的java代码实现
    五、python使用模块
  • 原文地址:https://www.cnblogs.com/anyixing-fly/p/13334451.html
Copyright © 2011-2022 走看看