莫比乌斯反演入门题目

整除分块

首先解决一个整除分块的问题。考虑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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章