莫比乌斯反演

形式1

已经有函数F(n)=∑  f(d),可以导出 f(n)=  ∑   μ(d)F(n/d)

                     d|n                            d|n

形式2

已经有F(n)∑   f(d),可以导出f(n)∑   μ(d/n)F(d)

                n|d                           n|d

 

bzoj2301 Problem b

题目大意:求gcd(x,y)=k(a<=x<=b,c<=y<=d)的数对个数。

思路:F(i)表示i|gcd(x,y)的数对个数,f(i)表示gcd(x,y)的数对个数,我们要求的就是f(k)。F(i)相对好求一些,F(i)=n/im/i⌋,

f(i)=∑(i|a)μ(a/i)F(a)=∑(i|a)μ(a|i)n/am/a ,然后我们只需要枚举⌊n/am/a⌋的值就可以了(这个值据说是2(根n+根m)。我们穷举a

找到所有⌊n/am/a相等的的位置(连续的),然后乘上预处理出来的mu函数的和就可以了。这里有一个小技巧,1...x的数列中,与n/i相等的值的最

后一位是(n/(n/i)),这样我们找到n、m右边界较小的就是一段相等的区间了。

同时,对于求一个区间内gcd=k的,我们可以通过将区间/k后找到互质的数对的个数就是答案了。

再同时,对于这道题中的答案要容斥原理一下,每次都处理成[1...x][1...y],然后答案就是work(b,d)-work(a,d)-work(c,b)+work(a,c)了。

这题中的技巧和化简思路都十分重要,这种穷举除数的思路据(某大神)说十分常用。

#include<iostream>

#include<cstdio>

#include<cstring>

#include<algorithm>

using namespace std;

int mu[50010]={0},prime[50010]={0},k;

bool flag[50010]={false};

void prework(int n)

{

    int i,j;

    mu[1]=1;

    for (i=2;i<=n;++i)

    {

        if (!flag[i])

        {

            prime[++prime[0]]=i;mu[i]=-1;

        }

        for (j=1;j<=prime[0]&&i*prime[j]<=n;++j)

        {

            flag[i*prime[j]]=true;

            if (i%prime[j]==0)

            {

                mu[i*prime[j]]=0;break;

            }

            else mu[i*prime[j]]=-mu[i];

        }

    }

    for (i=1;i<=n;++i) mu[i]+=mu[i-1];

}

int work(int x,int y)

{

    int i,last=0,ans=0;

    x/=k;y/=k;

    if (x>y) swap(x,y);

    for (i=1;i<=x;i=last+1)

    {

        last=min(x/(x/i),y/(y/i));

        ans+=(x/i)*(y/i)*(mu[last]-mu[i-1]);

    }

    return ans;

}

int main()

{

    int n,a,b,c,d,i,ans;

    prework(50000);

    scanf("%d",&n);

    for (i=1;i<=n;++i)

    {

        scanf("%d%d%d%d%d",&a,&b,&c,&d,&k);

        ans=work(b,d)-work(a-1,d)-work(c-1,b)+work(a-1,c-1);

        printf("%d\n",ans);

    }

}
View Code

 

bzoj3853GCD Array

题目大意:在一个数列上进行操作。1)读入p,d,v,对所有gcd(i,p)=d的ai+v;2)求$\sum_{i=1}^xa_i$.

思路:听了题解才明白是反演。我们对于一次修改操作可以看作$a_i+=[gcd(i,p)=d]v$,对上式进行化简可以得到

[gcd(i,p)=d]v=[gcd (\frac{i}{d},\frac{p}{d})=1]v

                  =v\sum_{e|\frac{i}{d},e| \frac{p}{d} } \mu{e}

                  = v\sum_{ed|i,e|\frac{p}{d}}\mu{e}

                  =\sum_{ed|i , e| \frac{p}{d}} \mu{e}v

我们维护一个数组$f_e$,令$a_i=\sum_{e|i}f_e$,修改的时候我们每次穷举$\frac{p}{d}$的约数x(也就是e),x*d的位置加上x*v,用树状数组维护;查询的时候ANS=\sum_{i=1}^xa_i

              =\sum_{i=1}^x\sum_{e|i}f(e)

               =\sum_{i=1}^x\left \lfloor\frac{x}{i}\right \rfloor f(i)

#include<iostream>

#include<cstdio>

#include<cstring>

#include<algorithm>

#include<vector>

#define maxnode 200005

using namespace std;

long long cc[maxnode]={0};

int n,prime[maxnode]={0},mu[maxnode]={0};

vector<int> yue[maxnode];

bool flag[maxnode]={false};

void pre()

{

    int i,j;

    mu[1]=1;

    for (i=2;i<maxnode;++i)

    {

        if (!flag[i])

        {

            prime[++prime[0]]=i;mu[i]=-1;

        }

        for (j=1;prime[j]*i<maxnode&&j<=prime[0];++j)

        {

            flag[prime[j]*i]=true;

            if (i%prime[j]) mu[i*prime[j]]=-mu[i];

            else

            {

                mu[i*prime[j]]=0;break;

            }

        }

    }

}

void getyue()

{

    int i,j;

    for (i=1;i<=maxnode;++i) yue[i].clear();

    for (i=1;i<maxnode;++i)

      for (j=i;j<maxnode;j+=i)

          yue[j].push_back(i);

}

int lowbit(int x){return x&(-x);}

long long ask(int x,int y)

{

    long long sum=0;

    --x;

    for (;y;y-=lowbit(y)) sum+=cc[y];

    for (;x;x-=lowbit(x)) sum-=cc[x];

    return sum;

}

void change(int x,int v)

{

    for (;x<=n;x+=lowbit(x)) cc[x]+=(long long)v;

}

int main()

{

    int m,i,j,p,v,d,op,ci=0;

    long long ans;

    pre();getyue();

    while(scanf("%d%d",&n,&m))

    {

      if (n==0&&m==0) break;

      memset(cc,0,sizeof(cc));++ci;

      printf("Case #%d:\n",ci);

      for (i=1;i<=m;++i)

      {

        scanf("%d",&op);

        if (op==1)

        {

            scanf("%d%d%d",&p,&d,&v);

            if (p%d) continue;

            p=p/d;

            for (j=0;j<yue[p].size();++j) change(yue[p][j]*d,mu[yue[p][j]]*v);

        }

        else

        {

            scanf("%d",&p);ans=0;

            for (j=1;j<=p;j=d+1)

            {

                d=(p/(p/j));

                ans+=(long long)(p/j)*ask(j,d);

            }

            printf("%lld\n",ans);

        }

      }

    }

}
View Code

 

bzoj2005 noi2010能量采集

题目大意:n×m的矩阵中,有n×m个点位于格点上,对于每个点,采集的能量是这个点和(0,0)连线上点的个数(不包含端点)×2+1,求采集所有点的能量和。

思路:对于一个点(x,y),采集的能量是2×(gcd(x,y)-1)+1,求这个的sigma。可以用反演,穷举1~min(n,m)作为gcd的值,求出个数后乘i加起来乘2-1就是答案了。(反演的过程同第一题)。

        其实并不需要这么麻烦,我们在反演中F(d)表示d|gcd(x,y)的个数,F(d)=(n/d)(m/d),那么我们用这个数减去gcd=2d、3d……id的个数就可以了,用f(d)表示gcd=d的个数,就是用F(d)-f(2d)-f(3d)-……f(id),我们倒着穷举gcd,就可以完成了。不需要反演和求mu,速度还++。。。

#include<iostream>

#include<cstdio>

#include<cstring>

#include<algorithm>

#define maxnode 100005

#define up 100000

using namespace std;

int mu[maxnode]={0},prime[maxnode]={0};

long long sum[maxnode]={0};

bool flag[maxnode]={0};

void getmu()

{

    int i,j;

    mu[1]=1;

    for (i=2;i<=up;++i)

    {

        if (!flag[i])

        {

            prime[++prime[0]]=i;mu[i]=-1;

        }

        for (j=1;j<=prime[0]&&i*prime[j]<=up;++j)

        {

            flag[i*prime[j]]=true;

            if (i%prime[j]==0){mu[i*prime[j]]=0;break;}

            else mu[i*prime[j]]=-mu[i];

        }

    }

    for (i=1;i<=up;++i) sum[i]=sum[i-1]+(long long)mu[i];

}

long long ask(int n,int m,int k)

{

    int i,j,last=0;

    long long ans=0;

    n/=k;m/=k;if (n>m) swap(n,m);

    for (i=1;i<=n;i=last+1)

    {

        last=min(n/(n/i),m/(m/i));

        ans+=(long long)(n/i)*(long long)(m/i)*(sum[last]-sum[i-1]);

    }

    return ans;

}

int main()

{

    int n,m,i,j;

    long long ans=0;

    scanf("%d%d",&n,&m);

    if (n>m) swap(n,m);getmu();

    for (i=1;i<=n;++i) ans+=i*ask(n,m,i);

    ans=ans*2-(long long)n*(long long)m;

    printf("%I64d\n",ans);

}
View Code

 

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