莫比乌斯(Mobius)反演知识整合

前言

  虽然我去年就已经学过这东西了,但是一直没有整理、归纳。而且我发现我对它的套路不够熟悉、对它的理解不够透彻,卒致GDOIDay2T1没切(详细事故记录戳这里)。所以,在此做一个知识整合。

引子

  先来看bzoj的一道题:Problem b

Problem

  给出n组询问,每组询问给出a、b、c、d、k五个整数,求有多少个数对(x,y),满足a≤x≤b,c≤y≤d,且gcd(x,y) = k。

Hint

  100%的数据满足:1≤n≤50000,1≤a≤b≤50000,1≤c≤d≤50000,1≤k≤50000

Solution

  首先,考虑转化一下。既然 A n s = x = a b y = c d δ ( x , y ) k δ 克罗内克函数),那么其实我们可以给a、b、c、d同除以一个k,转化为求 δ ( x , y ) , 1
  考虑设 A n s ( n , m ) = x = 1 n k y = 1 m k δ ( x , y ) 1 ,则原问题的答案= A n s ( b , d ) A n s ( a 1 , d ) A n s ( b , c 1 ) + A n s ( a 1 , c 1 )
  于是原问题转化为求解 A n s ( n , m )
  对于每个询问,都可以用 O ( b k d k ) 的时间复杂度求出Ans数组,然后计算Ans。
  然而,这依然不尽人意。
  考虑设 f ( k ) = x = 1 n y = 1 m δ ( x , y ) k ,先保证n<m,否则交换。
  直接计算f(k)很难。
  那么,再设 g ( k ) = x = 1 n y = 1 m [ k | ( x , y ) ] ,即满足k为(x,y)的约数的数对(x,y)的个数,则 g ( k ) = d = 1 n k f ( k d )
  g(k)的求解很容易,符合 x = k x 1 ( 1 x 1 n k ) , y = k y 1 ( 1 y 1 m k ) 的数对(x,y)的个数即为答案。所以 g ( k ) = n k m k
  然而,我们虽然貌似知道了很多,实际上并没有什么卵用。还有一个至关重要的难题亟待解决:如何用g(k)求解f(k)呢?
  
  这时,莫比乌斯反演闪亮登场!

反演

  反演到底是什么?
  我觉得这篇博客讲得十分详尽。大意如下:
  对于一个数列{f}来说,如果我们知道另外一个数列{g},满足如下条件:

g n = i = 0 n a i f i

  反演(inversion)就是利用{g}来表示出{f}:

f n = i = 0 n b i g i

  也就是说,反演的原理就是容斥
  下面的“莫比乌斯反演的引入”实质上就是反演。当然,如果你做的莫比乌斯反演的题多了,你会发觉,不仅是它,所有的莫比乌斯反演题的解法实质上都是反演。

莫比乌斯反演的引入

  由于这段东西过于重要,所以不得不写一下。
  但是,由于这段东西又过于冗长,所以我在网上截了两张图。
  声明:以下两张图片截图自百度百科
这里写图片描述
这里写图片描述

莫比乌斯函数

  定义 μ ( n ) 为自变量为n( n N )时的莫比乌斯函数。若设 n = x 1 a 1 x 2 a 2 x 3 a 3 · · · x p a p ,其中 x i 为n的质因子,则 μ 的取值如下:

μ ( n ) = { 1 n = 1 0 a i a a i > 1 ( 1 ) p a i = 1

  也就是说,当n=1时, μ ( n ) = 1 ;当n有重复质因子时, μ ( n ) = 0 ;当n无重复质因子时, μ ( n ) = ( 1 ) p
  因此, μ 为积性函数,可通过线筛顺带 O ( n ) 求出。
  那么,如何用这个函数求解上面那题呢?
  性质1

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

  这个怎么证明呢?
  依然设 n = x 1 a 1 x 2 a 2 x 3 a 3 · · · x p a p ,再设 d = x 1 b 1 x 2 b 2 x 3 b 3 · · · x p b p 。显然,若要d≠0,则须满足 b i 1 。设有q个bi为1,其余均为0,则可推出:

d | n = ( p q ) ( 1 ) q

  这和组合数扯上关系了。可以观察杨辉三角推出性质1,当然,我们也可以更严谨地,通过(被苹果砸死的)牛顿提出的二项式定理推出性质1。
  性质2 μ ( n ) 积性函数
  这个,看一看积性函数的定义,很显然,就不解释了。

莫比乌斯反演的证明

  下面的内容也比较重要,但是非常冗长,故贴上两张图片instead of手打:
这里写图片描述
这里写图片描述

回到引子

  重温一下,我们已知下式:

g ( k ) = d = 1 n k f ( k d )

  现在我们要通过g(k)求出f(k)。
  通过莫比乌斯反演的变形可知:

f ( k ) = d = 1 n k g ( k d ) μ ( d ) = d = 1 n k n k d m k d μ ( d )

  设T=k*d,则上式可以变得简洁些:

f ( k ) = d = 1 n k n T m T μ ( d )

  于是,对于每个询问,我们可以用 O ( m i n ( n k , m k ) ) 的时间复杂度求出f。
  但还是过不了。
  这时,我们考虑祭上莫比乌斯反演的又一强力套路:

分块

  首先,我们知道, n T 这东西是只有 1 n n 种取值的;同理, m T 也只有 m 种取值。
  想象在一条直线上, n T m T 的每一种取值都映射成该条直线上的一点,那么至多会有 n + m 个点。以最左最右两点取闭区间,则这个闭区间可视作一条线段,且此线段被它身上的 n + m 个点分成 n + m 1 条子线段。
  这些子区间内的 n T m T 的取值都是分别相同的,所以可以考虑一起计算。
  那么,考虑预处理出 μ 的前缀和,然后对于每种不同的 n T m T 段分别计算一下即可。
  于是,计算f的时间复杂度就被缩减为 O ( n + m )

例题

  当然,除过bzoj的Problem b,还是有不少简单的莫比乌斯反演题滴。
  bzoj上的DZY loves Math(也即jzoj上的Wyx)、bzoj上的于神之怒加强版(也即jzoj上的于神之怒)都是比较经典的莫比乌斯反演,套路大致相同。
  山东省选2015第一轮第一试的约数个数和(英文divisor,jzoj上也有)则是一道比较有思维难度的题,还要转换一下式子。

特点

  • 题目大意都比较简洁明了
  • 要求的答案都和gcd脱不了干系
  • 画风比较诡异
  • 要推很多很多、很长很长的式子,因而会蚕食你一页多的草稿纸
  • 一般出现在省选Day2T1

要点

  • 第零步:牢记莫比乌斯反演的几种变化形式
  • 第一步:将含有gcd的项移到后面,利用莫比乌斯函数的性质1将其转化成 d | k μ ( d )
  • 第二步:将含有 μ 的项移到前面,突破僵局,利用等差数列等公式拆掉后面的几个
  • 第三步:看到式子被化简成一重循环的时候,就可以上分块了

压轴大戏——谈笑风生

  不要以为我要谈笑风生,我说的是GDOIDay2T1。

Problem

  给定一个N个点、M条边的图,点i的点权为a[i]。对于边 u v ,其边权 l e n = i = 1 a [ u ] j = 1 a [ v ] δ ( i , j ) 1 ( i + j ) ,即经过这条边要花len秒。小熊维尼一开始在点1,他想在T秒内走到点n,而他能花P点能量使所有边的边权减少P且不能减至0。求最少需要花费多少能量才能在T秒内到达,以及在此前提下,最少要花费多少秒。

Hint

这里写图片描述

Solution

  比赛时我看了一会,发现这道题是两道题拼起来的题:求解边权+二分spfa。
  那么,二分spfa谁都会打,关键就在于求解边权了。
  However,在比赛时我并没有想到正解,甚至连莫比乌斯反演也没有想多久。
  下面现场推一波式子:

l e n = i = 1 a [ u ] j = 1 a [ v ] δ ( i , j ) 1 ( i + j )

  设a[u]<a[v],不满足则交换。
  根据莫比乌斯函数的性质1,可将 δ ( i , j ) 1 这个东西改换一下:

l e n = i = 1 a [ u ] j = 1 a [ v ] ( i + j ) x | ( i , j ) μ ( x )

  考虑将后面的 x | ( i , j ) μ ( x ) 挪到前面,然后用i、j枚举x的倍数:

l e n = x = 1 a [ u ] μ ( x ) i = 1 a [ u ] x j = 1 a [ v ] x x ( i + j )

  瞄到后面有一个x,果断将其挪到前面:

l e n = x = 1 a [ u ] μ ( x ) x i = 1 a [ u ] x j = 1 a [ v ] x ( i + j )

  考虑将后面的i、j分开处理,分别计算它们出现的次数,于是可以推出下式:

l e n = x = 1 a [ u ] μ ( x ) x [ ( i = 1 a [ u ] x i a [ v ] x ) + ( j = 1 a [ v ] x j a [ u ] x ) ]

  运用等差数列求和公式可以拆掉后面的两个

l e n = x = 1 a [ u ] μ ( x ) x ( a [ u ] x ( 1 + a [ u ] x ) 2 a [ v ] x + a [ v ] x ( 1 + a [ v ] x ) 2 a [ u ] x )

  我们发现后面要相加的冗长式子有公因式,于是合并同类项:

l e n = x = 1 a [ u ] μ ( x ) x a [ u ] x a [ v ] x ( 2 + a [ u ] x + a [ v ] x ) 2

  前面的 μ ( x ) x 可以预处理前缀和。
  后面的式子,我们发现它的取值只与 a [ u ] x a [ v ] x 有关,而它们至多只有 a [ u ] + a [ v ] 种不同取值,于是可以分块处理。
  时间复杂度:线筛求 μ ( x ) O ( m a x { a i } ) ,预处理前缀和 O ( m a x { a i } ) ,计算每条边边权 O ( i = 1 M a [ u i ] + a [ v i ] ) ,二分最短路 O ( l o g 2 m a x { l e n } )

Code

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
using namespace std;
#define ll long long
#define fo(i,a,b) for(i=a;i<=b;i++)
const int N=1e4+1,M=N<<2,A=1e5;
int i,j,mu[A+1],cnt,prime[A+1],x;
bool p[A+1];
ll sum[A+1];
void init()
{
    memset(p,1,sizeof p);
    mu[1]=sum[1]=1;
    fo(i,2,A)
    {
        if(p[i]){prime[++cnt]=i;mu[i]=-1;}
        for(j=1;j<=cnt&&(j==1||i%x)&&i*(x=prime[j])<=A;j++)
        {p[i*x]=0;mu[i*x]=i%x?-mu[i]:0;}
        sum[i]=sum[i-1]+(ll)mu[i]*i;
    }
}

int n,m,a[N],u,v;
ll T,Len;
void Mobius(ll n,ll m)
{
    if(n>m)swap(n,m);
    ll i=1,j,ni,mi;Len=0;
    while(i<=n)
    {
        j=min(n/(ni=n/i),m/(mi=m/i));
        Len+=(sum[j]-sum[i-1])*ni*mi*(2+ni+mi)>>1;
        i=j+1;
    }
}
int tot,tov[M],next[M],last[N];
ll len[M],maxP;
inline void link(int x,int y)
{
    tov[++tot]=y;
    len[tot]=Len;
    next[tot]=last[x];
    last[x]=tot;
}
void scan()
{
    scanf("%d%d%d",&n,&m,&T);
    fo(i,1,n)scanf("%d",&a[i]);
    if(n==1){printf("0 0");exit(0);}
    fo(i,1,m)
    {
        scanf("%d%d",&u,&v);
        Mobius(a[u],a[v]);
        maxP=max(maxP,Len);
        link(u,v);
        link(v,u);
    }
}

int y,head,tail,que[A*10];
ll dis[N];
bool exist[N];
inline ll calc(ll P){return max(len[i]-P,0ll);}
ll spfa(ll P)
{
    memset(dis,127,sizeof dis);
    head=dis[que[tail=1]=1]=0;
    memset(exist,0,sizeof exist);exist[1]=1;
    while(head<tail)
    {
        x=que[++head];
        for(i=last[x];i;i=next[i])
            if(dis[y=tov[i]]>dis[x]+calc(P))
            {
                dis[y]=dis[x]+calc(P);
                if(!exist[y])
                {
                    exist[que[++tail]=y]=1;
                    if(dis[que[head+1]]>dis[y])swap(que[head+1],que[tail]);
                }
            }
        exist[x]=0;
    }
    return dis[n];
}
void Bsort()
{
    ll l=0,r=maxP,mid;
    while(l<r)
    {
        mid=l+r>>1;
        if(spfa(mid)<=T)
                r=mid;
        else    l=mid+1;
    }
    printf("%lld %lld",l,spfa(l));
}

int main()
{
    freopen("magic.in","r",stdin);
    freopen("magic.out","w",stdout);
    init();
    scan();
    Bsort();
}

猜你喜欢

转载自blog.csdn.net/qq_36551189/article/details/80157350
今日推荐