莫比烏斯反演入門題目

整除分塊

首先解決一個整除分塊的問題。考慮ni\frac{n}{i}的值,ii11nn。會發現有大量的ii使得ni\frac{n}{i}有相同的值。例如,當nn取100時,ii從51到100都將得到相同的ni\frac{n}{i}。所以當要求
i=1100100i\sum_{i=1}^{100}\frac{100}{i}
時,完全不需要循環100次,只需要依次算出所有相同的值,相乘相加即可。
再考慮給一個數組aa,要求
i=1100ai100i\sum_{i=1}^{100}a_i\cdot\frac{100}{i}
此時可以預先求出數組aa的前綴和cc,同樣分段相乘相加即可。例如此時計算從51到100,就不需要循環50次,只需要計算
(c100c50)10051(c_{100}-c_{50})\cdot\frac{100}{51}即可

洛谷P2568: GCD

給定整數N,求[1, N]範圍內有滿足gcd(x, y)爲質數的數對一共有多少對。N在10710^7以內。
參照這裏基礎問題2的推演,答案就是
pkd=1Npkμ(d)NpkdNpkd\sum_{p_k}\sum_{d=1}^{\lfloor\frac{N}{p_k}\rfloor}\mu(d)\cdot\lfloor\frac{\lfloor\frac{N}{p_k}\rfloor}{d}\rfloor\cdot\lfloor\frac{\lfloor\frac{N}{p_k}\rfloor}{d}\rfloor
其中NN就是題目給定的數。如果求出莫比烏斯函數的前綴和,第二個求和記號就可以使用整除分塊來計算,然後對每一個質數循環即可。

#include <stdio.h>
typedef long long int llt;

int const SIZE = 10000007;//TODO
bool isComp[SIZE] = {false};
int P[SIZE] = {0};
int PCnt = 0;
int Euler[SIZE] = {0,1};
int Mobius[SIZE] = {0,1};
llt C[SIZE] = {0LL,1LL};//Ci = sigma(miu(i))
void sieveEulerMobius(){
    int tmp;
	for(int i=2;i<SIZE;++i){
		if ( !isComp[i] ) P[PCnt++] = i, Euler[i] = i - 1, Mobius[i] = -1;

		for(int j=0;j<PCnt&&i*P[j]<SIZE;++j){
			isComp[tmp=i*P[j]] = true;

			if ( 0 == i % P[j] ){
				Euler[tmp] = Euler[i] * P[j];
				Mobius[tmp] = 0;
				break;
			}else{
				Euler[tmp] = Euler[i] * ( P[j] - 1 );
				Mobius[tmp] = - Mobius[i];
			}
		}

		C[i] = C[i-1] + Mobius[i];
	}
}

int main(){
    sieveEulerMobius();

    int n;
    scanf("%d",&n);
    llt ans=0LL;
    for(int i=0;i<PCnt&&P[i]<=n;++i){//對每一個質數
        for(int r,j=1,nn=n/P[i];j<=nn;j=r+1){//整除分塊
            r = nn / (nn/j);
            ans += ( C[r] - C[j-1] ) * (nn/j) * (nn/j);
        }
    }
    printf("%lld\n",ans);
    return 0;
}

另外一種使用歐拉函數的做法,結合這裏基礎問題1和2的推導,答案還可以寫成
pk(2i=1Npkφ(d)1)\sum_{p_k}\Big(2\cdot\sum_{i=1}^{\lfloor\frac{N}{p_k}\rfloor}\varphi(d)-1\Big)
簡單推理一下,φ(i)\varphi(i)是不大於iiii互質的數量,則φ(i)\sum\varphi(i)就是ij(i,j)i\ge j且(i,j)互質的數量,將其乘二就得到所有互質的對數,其中只有(1,1)(1,1)計算了兩次,減去即可。

#include <stdio.h>
typedef long long int llt;

int const SIZE = 10000007;//TODO
bool isComp[SIZE] = {false};
int P[SIZE] = {0};
int PCnt = 0;
int Euler[SIZE] = {0,1};
int Mobius[SIZE] = {0,1};
llt C[SIZE] = {0LL,1LL};//Ci = sigma(phi(i))
void sieveEulerMobius(){
    int tmp;
	for(int i=2;i<SIZE;++i){
		if ( !isComp[i] ) P[PCnt++] = i, Euler[i] = i - 1, Mobius[i] = -1;

		for(int j=0;j<PCnt&&i*P[j]<SIZE;++j){
			isComp[tmp=i*P[j]] = true;

			if ( 0 == i % P[j] ){
				Euler[tmp] = Euler[i] * P[j];
				Mobius[tmp] = 0;
				break;
			}else{
				Euler[tmp] = Euler[i] * ( P[j] - 1 );
				Mobius[tmp] = - Mobius[i];
			}
		}

		C[i] = C[i-1] + Euler[i];
	}
}

int main(){
    sieveEulerMobius();

    int n;
    scanf("%d",&n);
    llt ans=0LL;
    for(int i=0;i<PCnt&&P[i]<=n;++i) ans += (C[n/P[i]]<<1)-1LL;
    printf("%lld\n",ans);
    return 0;
}

SPOJ VLATTICE

整點立方體,從(0,0,0)到(N,N,N),問從原點看過去能看到多少個點, N在一百萬。
點(2,2,2)、(2,4,2)之類顯然都看不見,可知能看到的就是gcd(x,y,z)爲1的那些點。把上一道題擴展一下可得答案
pkd=1Npkμ(d)NpkdNpkdNpkd\sum_{p_k}\sum_{d=1}^{\lfloor\frac{N}{p_k}\rfloor}\mu(d)\cdot\lfloor\frac{\lfloor\frac{N}{p_k}\rfloor}{d}\rfloor\cdot\lfloor\frac{\lfloor\frac{N}{p_k}\rfloor}{d}\rfloor\cdot\lfloor\frac{\lfloor\frac{N}{p_k}\rfloor}{d}\rfloor
當然這只是x、y、z全都是正整數即立方體內的解,還需要考慮其中一個座標爲0、即表面的解。表面解其實就是gcd(x,y)爲1的數量,然後再乘3即可。最後還要考慮數軸上的點,顯然只有3個。

#include <stdio.h>
#include <algorithm>
using namespace std;

typedef long long int llt;

int const SIZE = 1000007;//TODO
bool isComp[SIZE] = {false};
int P[SIZE] = {0};
int PCnt = 0;
int Euler[SIZE] = {0,1};
int Mobius[SIZE] = {0,1};
llt C[SIZE] = {0LL,1LL};//Ci = sigma(miu(i))
void sieveEulerMobius(){
    int tmp;
	for(int i=2;i<SIZE;++i){
		if ( !isComp[i] ) P[PCnt++] = i, Euler[i] = i - 1, Mobius[i] = -1;

		for(int j=0;j<PCnt&&i*P[j]<SIZE;++j){
			isComp[tmp=i*P[j]] = true;

			if ( 0 == i % P[j] ){
				Euler[tmp] = Euler[i] * P[j];
				Mobius[tmp] = 0;
				break;
			}else{
				Euler[tmp] = Euler[i] * ( P[j] - 1 );
				Mobius[tmp] = - Mobius[i];
			}
		}

		C[i] = C[i-1] + Mobius[i];
	}
}

int getUnsigned(){
	char ch = getchar();
	while( ch < '0' || ch > '9' ) ch = getchar();

	int ret = (int)(ch-'0');
	while( '0' <= ( ch = getchar() ) && ch <= '9' ) ret = ret * 10 + (int)(ch-'0');
	return ret;
}

int main(){
    //freopen("1.txt","r",stdin);
    sieveEulerMobius();

    int nofkase = getUnsigned();
    for(int kase=1;kase<=nofkase;++kase){
        int n=getUnsigned();


        llt ans=0LL;
        //體內的點
        for(int r,i=1;i<=n;i=r+1){//整除分塊
            r = min(n/(n/i),n/(n/i));
            ans += ( C[r] - C[i-1] ) * (n/i) * (n/i) * (n/i);
        }
        //一面的點
        llt ans2 = 0LL;
        for(int r,i=1;i<=n;i=r+1){//整除分塊
            r = min(n/(n/i),n/(n/i));
            ans2 += ( C[r] - C[i-1] ) * (n/i) * (n/i);
        }
        //再加3個座標軸上的點
        printf("%lld\n",ans+ans2*3LL+3LL);
    }

    return 0;
}

hdu1695: GCD

xx[1,b][1,b]yy[1,d][1,d],問有多少對(x,y)(x,y)滿足gcd=kgcd=kxxyy不要求秩序, 實際上就是問gcd(x/k,y/k)=1gcd(x/k,y/k)=1的數量。令n=b/k,m=d/kn=b/k,m=d/k,且設nmn\le m,參見這裏基礎問題3,答案就是
i=1nj=1m[gcd(i,j)==1]=d=1nμ(d)n/dm/d\sum_{i=1}^{n}\sum_{j=1}^{m}[gcd(i,j)==1]=\sum_{d=1}^{n}\mu(d)\cdot\lfloor{n/d}\rfloor\cdot\lfloor{m/d}\rfloor
當然,這裏要求不計秩序,也就是(5,7)和(7,5)認爲是沒有區別的,所以要去重。很顯然,只有當y落在[1, n]範圍內纔有可能重複,所以重複的數量其實就是[1, n]中互質的數量的一半。

#include <stdio.h>
#include <algorithm>
using namespace std;

typedef long long int llt;

int const SIZE = 100007;//TODO
bool isComp[SIZE] = {false};
int P[SIZE] = {0};
int PCnt = 0;
int Euler[SIZE] = {0,1};
int Mobius[SIZE] = {0,1};
llt C[SIZE] = {0LL,1LL};//Ci = sigma(miu(i))
void sieveEulerMobius(){
    int tmp;
	for(int i=2;i<SIZE;++i){
		if ( !isComp[i] ) P[PCnt++] = i, Euler[i] = i - 1, Mobius[i] = -1;

		for(int j=0;j<PCnt&&i*P[j]<SIZE;++j){
			isComp[tmp=i*P[j]] = true;

			if ( 0 == i % P[j] ){
				Euler[tmp] = Euler[i] * P[j];
				Mobius[tmp] = 0;
				break;
			}else{
				Euler[tmp] = Euler[i] * ( P[j] - 1 );
				Mobius[tmp] = - Mobius[i];
			}
		}

		C[i] = C[i-1] + Mobius[i];
	}
}

int getUnsigned(){
	char ch = getchar();
	while( ch < '0' || ch > '9' ) ch = getchar();

	int ret = (int)(ch-'0');
	while( '0' <= ( ch = getchar() ) && ch <= '9' ) ret = ret * 10 + (int)(ch-'0');
	return ret;
}

int main(){
    //freopen("1.txt","r",stdin);
    sieveEulerMobius();

    int nofkase = getUnsigned();
    for(int kase=1;kase<=nofkase;++kase){
        int a=getUnsigned();
        int b=getUnsigned();
        int c=getUnsigned();
        int d=getUnsigned();
        int k=getUnsigned();
        
        if(!k){
            printf("Case %d: 0\n",kase);
            continue;
        }

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

        llt ans=0LL;
        //計算出所有互異的數對,但題目要求不計秩序,還要減掉一部分
        for(int r,i=1;i<=n;i=r+1){//整除分塊
            r = min(n/(n/i),m/(m/i));
            ans += ( C[r] - C[i-1] ) * (n/i) * (m/i);
        }
        //要減去的部分就是[1~n]的數對
        llt ans2 = 0LL;
        for(int r,i=1;i<=n;i=r+1){
            r = n / (n/i);
            ans2 += ( C[r] - C[i-1] ) * (n/i) * (n/i);
        }
        printf("Case %d: %lld\n",kase,ans-(ans2>>1));
    }

    return 0;
}

BZOJ 2005: 能量採集

給定n和m,假定nmn\le m,本質上就是問
i=1n(xnym[gcd(x,y)==i])(2i1)\sum_{i=1}^{n}\Big(\sum_{x\le n}\sum_{y\le m}[gcd(x,y)==i]\Big)\cdot(2i-1)

#include <stdio.h>
#include <algorithm>
using namespace std;

typedef long long int llt;

int const SIZE = 100007;//TODO
bool isComp[SIZE] = {false};
int P[SIZE] = {0};
int PCnt = 0;
int Euler[SIZE] = {0,1};
int Mobius[SIZE] = {0,1};
llt C[SIZE] = {0,1};
void sieveEulerMobius(){
    llt tmp;
	for(int i=2;i<SIZE;++i){
		if ( !isComp[i] ) P[PCnt++] = i, Euler[i] = i - 1, Mobius[i] = -1;

		for(int j=0;j<PCnt&&(tmp=i*P[j])<SIZE;++j){
			isComp[tmp] = true;

			if ( 0 == i % P[j] ){
				Euler[tmp] = Euler[i] * P[j];
				Mobius[tmp] = 0;
				break;
			}else{
				Euler[tmp] = Euler[i] * ( P[j] - 1 );
				Mobius[tmp] = - Mobius[i];
			}
		}
		C[i] = C[i-1] + Mobius[i];
	}
}

int main(){
    sieveEulerMobius();

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

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

    llt ans=0LL;
    for(int g=1;g<=n;++g){
        int nn = n/g;
        int mm = m/g;

        llt cnt = 0LL;
        for(int r,j=1;j<=nn;j=r+1){
            r = min(nn/(nn/j),mm/(mm/j));
            cnt += (C[r]-C[j-1]) * (nn/j) * (mm/j);
        }

        ans += cnt * ( g + g - 1);
    }
    printf("%lld\n",ans);
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章