[kuangbin]莫比烏斯反演——入門

說是入門吧,其實寫了那幾題之後還是不會(

莫比烏斯反演是數論中比較重要的部分了,公式到處都有,重點就是怎麼求出μ函數和如何反演,目前做到的題目好像都是利用的變形後的公式,就是:
這裏寫圖片描述
變形前統計的是能整出n的那些,變形後統計的是n的倍數的那些。

專題裏的所有題目都是跟gcd有關,讓人有點懷疑莫比烏斯只能處理這類問題嗎,莫比烏斯反演主要就是你要求f(n)但是求這個的複雜度太高,然而F(n)可以直接得出,然後就利用反演就能比較方便的得出f(n)。
就如gcd來說,你要求平面上gcd(x,y)=1的點對,暴力枚舉的話就n^2*gcd的複雜度咯。但是對於gcd(x,y)=i的倍數的點對數量,只要x是i的倍數,y是i的倍數的所有點對就都滿足了,數量也就是(x/i)*(y/i),然後我們利用反演就能在較低的複雜度求出來了。
由於在枚舉i的時候會出現連續的i,(x/i)和(y/i)的值都是不變的,所以可以利用這個加速計算

A - Visible Lattice Points——SPOJ - VLATTICE

三維空間內求gcd(x,y,z)=1的點的數量,要特殊處理一個座標爲0和兩個座標爲0的點。

#include<bits/stdc++.h>
using namespace std;
#define MEM(a,b) memset(a,b,sizeof(a));
typedef long long ll;
const int maxn=1000005;
const int maxq=300005;
const int maxm=3000005;
const int inf=0x3f3f3f3f;
ll n,m,q;
int mu[maxn];
bool vis[maxn];
int prime[maxn];
int tot;
void get_mu(){
    mu[1]=1;
    for(int i=2;i<maxn;i++){
        if(!vis[i]){
            prime[tot++]=i;
            mu[i]=-1;
        }
        for(int j=0;prime[j]*i<maxn&&j<tot;j++){
            vis[prime[j]*i]=1;
            if(i%prime[j]==0){mu[prime[j]*i]=0;break;}
            mu[prime[j]*i]=-mu[i];
        }
    }
}

int main()
{
  get_mu();
  int t;
  scanf("%d",&t);
  while(t--){
    scanf("%lld",&n);
    ll ans=3;
    for(int i=1;i<=n;i++){
        ans=ans+mu[i]*(n/i)*(n/i)*(n/i);
    }
    for(int i=1;i<=n;i++){
        ans=ans+mu[i]*(n/i)*(n/i)*3;
    }
    printf("%lld\n",ans);
  }

   return 0;
}

B - GCD——HDU - 1695

求平面上x in a…b, y in c…d that GCD(x, y) = k
問題轉化成x in a/k…b/k, y in c/k…d/k that GCD(x, y) = 1
重點是要去重,因爲(3,5)和(5,3)是算重複的,統計F(i)時變成
ll lef=ll(b)/i;
ll rig=ll(d/i);
ll mm=min(lef,rig);
F(i)=(lef*rig-mm*(mm-1)/2);

#include<bits/stdc++.h>
using namespace std;
#define MEM(a,b) memset(a,b,sizeof(a));
typedef long long ll;
const int maxn=110005;
const int maxq=300005;
const int maxm=3000005;
const int inf=0x3f3f3f3f;
ll a,b,c,d,k;
int mu[maxn];
bool vis[maxn];
int prime[maxn];
int tot;
void get_mu(){
    mu[1]=1;
    for(int i=2;i<maxn;i++){
        if(!vis[i]){
            prime[tot++]=i;
            mu[i]=-1;
        }
        for(int j=0;prime[j]*i<maxn&&j<tot;j++){
            vis[prime[j]*i]=1;
            if(i%prime[j]==0){mu[prime[j]*i]=0;break;}
            mu[prime[j]*i]=-mu[i];
        }
    }
}

int main()
{
  get_mu();
  int t;
  int cas=0;
  scanf("%d",&t);
  while(t--){
    cas++;
    scanf("%d%d%d%d%d",&a,&b,&c,&d,&k);
    printf("Case %d: ",cas);
    if(k==0){printf("0\n");continue;}
    b/=k;
    d/=k;
    if(b==0||d==0)printf("0\n");
    else {
        ll ans=0;
        for(int i=1;i<=min(b,d);i++){
            ll lef=ll(b)/i;
            ll rig=ll(d/i);
            ll mm=min(lef,rig);
            ans+=mu[i]*(lef*rig-mm*(mm-1)/2);
        }
        printf("%lld\n",ans);
    }
    }
   return 0;
}

C - Mophues——HDU - 4746

還不是很懂/。。。。

//其實根本就還不會這題
#include<bits/stdc++.h>
using namespace std;
#define MEM(a,b) memset(a,b,sizeof(a));
typedef long long ll;
const int maxn=500005;
const int maxm=3000005;
const int inf=0x3f3f3f3f;
int n,m,p;
int mu[maxn];
int cnt[maxn];
bool vis[maxn];
int prime[maxn];
int f[maxn][20];
int tot;

void init(){
    mu[1]=1;
    for(int i=2;i<maxn;i++){
        if(!vis[i]){
            prime[tot++]=i;
            mu[i]=-1;
            cnt[i]=1;
        }
        for(int j=0;j<tot&&prime[j]*i<maxn;j++){
            vis[prime[j]*i]=1;
            cnt[i*prime[j]]=cnt[i]+1;
            if(i%prime[j]==0){mu[prime[j]*i]=0;break;}
            mu[prime[j]*i]=-mu[i];
        }
    }
    for(int i=1;i<maxn;i++){
        for(int j=i;j<maxn;j+=i){
            f[j][cnt[i]]+=mu[j/i];
        }
    }
    for(int i=1;i<maxn;i++){
        for(int j=1;j<20;j++){
            f[i][j]+=f[i][j-1];
        }
    }
    for(int i=1;i<maxn;i++){
        for(int j=0;j<20;j++){
            f[i][j]+=f[i-1][j];
        }
    }
}


int main(){
   init();
   int T;
   scanf("%d",&T);
    while(T--){
   scanf("%d%d%d",&n,&m,&p);
   if(p>18){
    printf("%lld\n",ll(n)*m);
    continue;
   }
    ll ans=0;
    int last;
    for(int i=1;i<min(n,m);i=last+1){
        last=min(n/(n/i),m/(m/i));
        ans+=ll(f[last][p]-f[i-1][p])*(n/i)*(m/i);
    }
    printf("%lld\n",ans);
    }
   return 0;
}

E - Gcd——HYSBZ - 2818

統計gcd爲質數的點的數量
這裏寫圖片描述
根據公式右邊,f(prime[i])=sigmaμ(d/prime[i])F(d)
這裏的d就是質數的倍數,開一個數組存每一個F(d)對應的sigma μ(d/prime)然後求和統計,我覺得直接枚舉質數來做也行?

#include<bits/stdc++.h>
using namespace std;
#define MEM(a,b) memset(a,b,sizeof(a));
typedef long long ll;
const int maxn=10000105;
const int maxm=3000005;
const int inf=0x3f3f3f3f;
int n;
bool vis[maxn];
int prime[maxn];
int mu[maxn];
int sum[maxn];
int tot;
void init(int maxn){
    mu[1]=1;
    for(int i=2;i<maxn;i++){
        if(!vis[i]){
            prime[tot++]=i;
            mu[i]=-1;
        }
        for(int j=0;prime[j]*i<maxn&&j<tot;j++){
            vis[prime[j]*i]=1;
            if(i%prime[j]==0){mu[prime[j]*i]=0;break;}
            mu[prime[j]*i]=-mu[i];
        }
    }
}

int main(){
   scanf("%d",&n);
   init(n+4);
   ll ans=0;
   for(int i=0;i<tot;i++){
        for(ll j=prime[i];j<=n;j+=prime[i]){
            sum[j]+=mu[j/prime[i]];
        }
   }
   for(int i=1;i<=n;i++){
        ans+=sum[i]*ll(n/i)*(n/i);
   }
   printf("%lld\n",ans);
   return 0;
}

F - 能量採集——HYSBZ - 2005

對於gcd爲i的點,能量損失爲gcd(x,y)*2-1,其餘的跟之前的問題都差不多,使用了加速計算。

#include<bits/stdc++.h>
using namespace std;
#define MEM(a,b) memset(a,b,sizeof(a));
typedef long long ll;
const int maxn=100005;
const int maxm=3000005;
const int inf=0x3f3f3f3f;
int n,m;
int mu[maxn];
bool vis[maxn];
int prime[maxn];
int sum[maxn];
int tot;

void init(int maxn){
    mu[1]=1;
    for(int i=2;i<maxn;i++){
        if(!vis[i]){
            prime[tot++]=i;
            mu[i]=-1;
        }
        for(int j=0;j<tot&&prime[j]*i<maxn;j++){
            vis[prime[j]*i]=1;
            if(i%prime[j]==0){mu[prime[j]*i]=0;break;}
            mu[prime[j]*i]=-mu[i];
        }
    }
    for(int i=1;i<maxn;i++){
        sum[i]=sum[i-1]+mu[i];
    }
}

ll cal(int x,int y,int k){
    ll ans=0;
    int last;
    x/=k,y/=k;
    for(int i=1;i<=min(x,y);i=last+1){
        last=min(x/(x/i),y/(y/i));//i到last這段x/i and y/i都相等
        ans+=ll(sum[last]-sum[i-1])*(x/i)*(y/i);
    }
    return ans;
}

int main(){
    scanf("%d%d",&n,&m);
    init(max(n,m)+1);
    ll ans=0;
    for(int i=1;i<=min(n,m);i++){
        ans+=(2*i-1)*cal(n,m,i);
    }
    printf("%lld\n",ans);
   return 0;
}

G - Problem b——HYSBZ - 2301

也是跟之前的題目差不多,就是統計區間換了,加加減減一下就行了。

#include<bits/stdc++.h>
using namespace std;
#define MEM(a,b) memset(a,b,sizeof(a));
typedef long long ll;
const int maxn=50005;
const int maxm=3000005;
const int inf=0x3f3f3f3f;
int a,b,c,d,k;
int mu[maxn];
bool vis[maxn];
int prime[maxn];
int sum[maxn];
int tot;

void init(){
    mu[1]=1;
    for(int i=2;i<maxn;i++){
        if(!vis[i]){
            prime[tot++]=i;
            mu[i]=-1;
        }
        for(int j=0;j<tot&&prime[j]*i<maxn;j++){
            vis[prime[j]*i]=1;
            if(i%prime[j]==0){mu[prime[j]*i]=0;break;}
            mu[prime[j]*i]=-mu[i];
        }
    }
    for(int i=1;i<maxn;i++){
        sum[i]=sum[i-1]+mu[i];
    }
}

ll cal(int x,int y,int k){
    ll ans=0;
    int last;
    x/=k,y/=k;
    for(int i=1;i<=min(x,y);i=last+1){
        last=min(x/(x/i),y/(y/i));//i到last這段x/i and y/i都相等
        ans+=ll(sum[last]-sum[i-1])*(x/i)*(y/i);
    }
    return ans;
}

int main(){
   init();
   int T;
   scanf("%d",&T);
    while(T--){
   scanf("%d%d%d%d%d",&a,&b,&c,&d,&k);
    ll ans=0;
    a--,c--;
    ans+=cal(b,d,k);
    if(c)
    ans-=cal(b,c,k);
    if(d)
    ans-=cal(a,d,k);
    if(c&&d)
    ans+=cal(a,c,k);
    printf("%lld\n",ans);
    }
   return 0;
}
發佈了113 篇原創文章 · 獲贊 41 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章