1. 程式人生 > >莫比烏斯函式ACM

莫比烏斯函式ACM

莫比烏斯入門請耐心往下看:






我的指標和我一樣找不到物件

OK.現在可以開始刷題了。

從區間[1, b]和[1,d]中分別選一個x, y,使得gcd(x, y) == k, 求滿足條件的xy的對數(不區分xy的順序)

分析:轉換成求[1,b/k],[1,d/k]中gcd(x,y)==1的(x,y)對數.需要知道下面這個定理


然後把這個定理換成程式碼就好

#include<vector>
#include<cmath>
#include<algorithm>
#include<string>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#define sf(n)    scanf("%d", &n)
#define sff(a,b) scanf("%d %d", &a, &b)
#define sfff(a,b,c) scanf("%d %d %d", &a, &b, &c)
#define MT(x,i)  memset(x,i,sizeof(x))
typedef long long LL;
using namespace std;

const int MAXN = 100000;
/*//線性篩法求莫比烏斯函式
bool check[MAXN+10];
int prime[MAXN+10];
int mu[MAXN+10];
void Moblus()
{
    memset(check,false,sizeof(check));
    mu[1] = 1;
    int tot = 0;
    for(int i = 2; i <= MAXN; i++)
    {
        if( !check[i] )
        {
            prime[tot++] = i;
            mu[i] = -1;
        }
        for(int j = 0; j < tot; j++)
        {
            if(i * prime[j] > MAXN) break;
            check[i * prime[j]] = true;
            if( i % prime[j] == 0)
            {
                mu[i * prime[j]] = 0;
                break;
            }
            else
            {
                mu[i * prime[j]] = -mu[i];
            }
        }
    }
}
*/
int mu[MAXN+10]={0};
void getMu(){//n*logn
    for(int i=1; i<=MAXN; i++)
    {
        int target = i==1?1:0;
        int delta = target - mu[i];
        mu[i]=delta;
        for(int j=i*2; j<=MAXN; j+=i)
            mu[j]+=delta;
    }
}
int main()
{
    getMu();
    //Moblus();
    //for(int i=1; i<=50; i++) printf("%d %d\n",i,mu[i]);
    int a,b,c,d,k,T,cas=1;
    for(sf(T);T--;){
        scanf("%d%d%d%d%d",&a,&b,&c,&d,&k);
        if(k==0){printf("Case %d: 0\n",cas++);continue; }
        b/=k,d/=k;
        if(b>d) swap(b,d);
        LL ans=0,rep=0;//答案,重複個數
        for(int i=1;i<=b;i++){
            ans+=(LL)mu[i]*(b/i)*(d/i);
            rep+=(LL)mu[i]*(b/i)*(b/i);
        }
        ans=ans-rep/2;
        printf("Case %d: %I64d\n",cas++,ans);
    }
    return 0;
}

求莫比烏斯函式模板如下:兩種求法,第一種更快,但程式碼量大。第二種程式碼少,但耗時久。

const int MAXN = 100000;
//線性篩法求莫比烏斯函式
bool check[MAXN+10];
int prime[MAXN+10];
int mu[MAXN+10];
void Moblus()
{
    memset(check,false,sizeof(check));
    mu[1] = 1;
    int tot = 0;
    for(int i = 2; i <= MAXN; i++)
    {
        if( !check[i] ){
            prime[tot++] = i;
            mu[i] = -1;
        }
        for(int j = 0; j < tot; j++)
        {
            if(i * prime[j] > MAXN) break;
            check[i * prime[j]] = true;
            if( i % prime[j] == 0){
                mu[i * prime[j]] = 0;
                break;
            }else{
                mu[i * prime[j]] = -mu[i];
            }
        }
    }
}
/*int mu[MAXN+10]={0};
void getMu(){   //n*logn   遞推篩法
    for(int i=1; i<=MAXN; i++)
    {
        int target = i==1?1:0;
        int delta = target - mu[i];
        mu[i]=delta;
        for(int j=i*2; j<=MAXN; j+=i)
            mu[j]+=delta;
    }
}*/

1  2  3  4  5  6  7  8  9  10  11  12  13

0 -1 -1  0 -1  1 -1  0  0   1  -1   0  -1

 http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=37166

對於給出的 n 個詢問, 每次求有多少個數對(x,y), 滿足 a≤x≤b, c≤y≤d, 且 gcd(x,y) = k,

gcd(x,y)函式為 x 和 y 的最大公約數。

1≤n≤50000,1≤a≤b≤50000,1≤c≤d≤50000,1≤k≤50000

資料量大,需要分段優化

#include<algorithm>
#include<cstdio>
#include<cstring>
#define sf(n)    scanf("%d", &n)
typedef long long LL;
using namespace std;
const int MAXN = 50000+5;
bool check[MAXN+10];
int prime[MAXN+10],mu[MAXN+10],sum[MAXN+10];
void Moblus()
{
    memset(check,false,sizeof(check));
    mu[1] = 1;
    int tot = 0;
    for(int i = 2; i <= MAXN; i++){
        if( !check[i] ){
            prime[tot++] = i;
            mu[i] = -1;
        }
        for(int j = 0; j < tot; j++){
            if(i * prime[j] > MAXN) break;
            check[i * prime[j]] = true;
            if( i % prime[j] == 0){
                mu[i * prime[j]] = 0;
                break;
            }
            else mu[i * prime[j]] = -mu[i];
        }
    }
}
LL calc(int n,int m)//計算[1,n]和[1,m]中gcd==1的數對。(3,2),(2,3)算兩個
{
    LL ret=0;
    if(n>m) swap(n,m);
    for(int i=1,next=0;i<=n;i=next+1){
        int d1=n/i,d2=m/i;
        int next1=n/d1,next2=m/d2;
        next=min(next1,next2);
        ret+=(LL)(sum[next]-sum[i-1])*d1*d2;
    }
    return ret;
}
int main()
{
    Moblus();
    for(int i=1;i<=MAXN;i++) sum[i]=sum[i-1]+mu[i];
    int a,b,c,d,k,T;
    for(sf(T);T--;){
        scanf("%d%d%d%d%d",&a,&b,&c,&d,&k);
        LL ans=calc(b/k,d/k)-calc((a-1)/k,d/k)-calc((c-1)/k,b/k)+calc((a-1)/k,(c-1)/k);
        printf("%lld\n",ans);
    }
    return 0;
}


求一個n*n*n的晶體,有多少點可以在(0,0,0)處可以直接看到。

也就是不是有點在它們之間,即它們gcd(x,y,z)==1.  加上特例:3個軸+3個平面

/*

這題是求(0,0,0)~(N,N,N)中gcd(a,b,c)=1的點的個數

顯然就是莫比烏斯反演的板題了...很容易就能得出F(1) =sigma(miu(d)*(n/d)*(n/d)*(n/d))

但這求出來的是(1,1,1)~(N,N,N)中的點的個數...還要加上含0的三個平面內的點...

因為對稱...所以直接加上二維上的整點數*3

最後再加上(0,0,1),(1,0,0),(0,1,0)三個點...就OK了...

*/


#include<vector>
#include<cmath>
#include<algorithm>
#include<string>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#define sf(n)    scanf("%d", &n)
#define sff(a,b) scanf("%d %d", &a, &b)
#define sfff(a,b,c) scanf("%d %d %d", &a, &b, &c)
#define MT(x,i)  memset(x,i,sizeof(x))
typedef long long LL;
using namespace std;

const int MAXN = 1000000;
//線性篩法求莫比烏斯函式
bool check[MAXN+10];
int prime[MAXN+10];
int mu[MAXN+10];
int sum[MAXN+10];
void Moblus()
{
    memset(check,false,sizeof(check));
    mu[1] = 1;
    int tot = 0;
    for(int i = 2; i <= MAXN; i++)
    {
        if( !check[i] )
        {
            prime[tot++] = i;
            mu[i] = -1;
        }
        for(int j = 0; j < tot; j++)
        {
            if(i * prime[j] > MAXN) break;
            check[i * prime[j]] = true;
            if( i % prime[j] == 0)
            {
                mu[i * prime[j]] = 0;
                break;
            }
            else
            {
                mu[i * prime[j]] = -mu[i];
            }
        }
    }
}
int main()
{
    Moblus();
    int T,n;
    for(sf(T);T--;){
        scanf("%d",&n);
        LL ans=3;//x,y,z軸上面的三點.(1,0,0) (0,1,0) (0,0,1);
        for(int i=1;i<=n;i++)
            ans+=(LL)mu[i]*(n/i)*(n/i)*(n/i+3);
            //ans+= (LL)mu[i]*(n/i)*(n/i)*(n/i)+(LL)mu[i]*(n/i)*(n/i)*3; //三維+二維*3
        printf("%lld\n",ans);
    }
    return 0;
}
/*
這題是求(0,0,0)~(N,N,N)中gcd(a,b,c)=1的點的個數
顯然就是莫比烏斯反演的板題了...很容易就能得出F(1) = sigma(miu(d)*(n/d)*(n/d)*(n/d))
但這求出來的是(1,1,1)~(N,N,N)中的點的個數...還要加上含0的三個平面內的點...
因為對稱...所以直接加上二維上的整點數*3
最後再加上(0,0,1),(1,0,0),(0,1,0)三個點...就OK了...
*/