莫比乌斯反演之入门

前言

很久以前就学了莫比乌斯反演,然而一直都木有来写一个总结,省选完后今日来补坑…

姿势

其实莫比乌斯反演就是一个公式…

F(n)=d|nf(d)f(d)=d|nμ(d)F(nd)

证明如下:

d|nμ(d)F(nd)=d|nμ(d)a|ndf(a)

=d|nf(d)a|ndμ(a)=f(n)

之后的问题就变成了如何求莫比乌斯函数?
d=1 ,则 μ(d)=1
d=p1p2...pk ,则 μ(d)=(1)k
否则 μ(d)=0

u[1]=1;
    for(int i=2, k;i<MAXN;++i)
    {
        if(!flag[i])prime[++tot]=i, u[i]=-1;
        for(int j=1;j<=tot&&(k=i*prime[j])<MAXN;++j)
        {
            flag[k]=1;
            if(i%prime[j]==0){u[k]=0;break;}
            u[k]=-u[i];
        }
    }

下面是莫比乌斯反演的一个有趣的性质…

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

证明:
莫比乌斯反演之入门_第1张图片

例题

还是直接上题吧…

hdu1695
转送门
题目大意:求满足 gcd(x,y)=k(1xb 1yd) 的无序数对 (x,y) 的个数。

莫比乌斯反演最基本的应用,当然也可以选择容斥原理。突然觉得莫比乌斯反演就是容斥原理…

F(d) 表示 d|gcd(x,y) 的对数,那么 f(d) 就表示 gcd(x,y)=d 的对数。
很显然 F(d)=(x/d)(y/d)
所以就直接上莫比乌斯反演吧….

当初写的时候一直RE,然后发现k竟然可以为0!!!

#include <iostream>
#include <cstdio>
#define MAXN 100005
#define LL long long int
using namespace std;

int a, b, c, d, k;
bool flag[MAXN];
int u[MAXN], prime[MAXN], cnt;
void mobius()
{
    u[1]=1;
    for(int i=2;i<MAXN;++i)
    {
        if(!flag[i])
        {
            prime[++cnt]=i;
            u[i]=-1;
        }
        for(int j=1;prime[j]*i<MAXN&&j<=cnt;++j)
        {
            flag[prime[j]*i]=1;
            if(i%prime[j]==0){u[i*prime[j]]=0;break;}
            u[i*prime[j]]=-u[i];
        }
    }
}

LL solve(LL n,LL m)
{
    n/=k, m/=k; LL ans=0; for(int i=1;i<=n;++i) { ans+=(n/i)*(m/i)*u[i];
    }
    return ans;
}

int main()
{
    mobius();
    int cas, CNT=0;
    scanf("%d",&cas);
    while(cas--)
    {
        scanf("%d%d%d%d%d",&a,&b,&c,&d,&k);
        if(k==0)
        {
            printf("Case %d: 0\n",++CNT);
            continue;
        }
        if(b>d)swap(b,d);
        printf("Case %d: %I64d\n",++CNT,solve(b,d)-solve(b,b)/2);
    }
    return 0;
}

SPOJVLATTICE
转送门
大意说不太清楚…
然后感觉就是求 gcd(a,b,c)=1 的对数…
就直接上吧…

bzoj2301
转送门
和上面那题特别相像,然后我就交了一发…T辣…
然后有一个最基本的分块优化…
F(d)=ndmd
其实不同的F只有 2n 个…
所以有一些可以一起算的…

LL solve(int a,int b)
{
    if(a>b)swap(a,b);
    LL ans=0;
    for(int i=1, last;i<=a;i=last+1)
    {
        last=min(a/(a/i),b/(b/i));
        ans+=(f[last]-f[i-1])*(a/i)*(b/i);
    }
    return ans;
}

bzoj2820
权限题无转送门,有一题一模一样的题在spoj上 转送门
题目大意:求有多少数对 (x,y)(1xn,1ym) 满足 gcd(x,y) 为质数。

一个很native的想法是枚举质数,然后就是上面那道题辣…
然后T的漂亮啊…

我们来看一看柿子:

ans=pndnμ(d)npdmpd

t=pd ,然后柿子就变成了:
ans=t=1np|tnμ(tp)ntmt=t=1nntmtp|tμ(tp)

然后 p|tμ(tp) 就可以用筛法乱搞搞辣…

int u[MAXN], prime[MAXN], tot;
bool flag[MAXN];
LL f[MAXN];
void init()
{
    u[1]=1;
    for(int i=2, k;i<MAXN;++i)
    {
        if(!flag[i])prime[++tot]=i, u[i]=-1;
        for(int j=1;j<=tot&&(k=i*prime[j])<MAXN;++j)
        {
            flag[k]=1;
            if(i%prime[j]==0){u[k]=0;break;}
            u[k]=-u[i];
        }
    }
    for(int i=1, k;i<=tot;++i)
    {
        k=prime[i];
        for(int j=1;j*k<MAXN;++j)f[j*k]+=u[j];
    }
    for(int i=1;i<MAXN;++i)f[i]+=f[i-1];
}

LL solve(int a,int b)
{
    if(a>b)swap(a,b);
    LL ans=0;
    for(int i=1, last;i<=a;i=last+1)
    {
        last=min(a/(a/i),b/(b/i));
        ans+=(f[last]-f[i-1])*(a/i)*(b/i);
    }
    return ans;
}

int main()
{
    init();
    int cas, u, v;
    GET(cas);
    while(cas--)
    {
        GET(u), GET(v);
        printf("%lld\n",solve(u,v));
    }
    return 0;
}

你可能感兴趣的:(莫比乌斯反演)