莫反的一些练习题(1)

莫比乌斯反演
莫反的一些练习题(2)


1.树林里的树. Trees in a Wood

原题链接


题目大意

i=aaj=bb[|gcd(i,j)|=1](2a+1)(2b+1)1(a2000b2×106),结果保留7位小数


分析

一道非常基础的莫反题

可以发现4个象限对答案的贡献是一样的,考虑只处理第一象限,最后将答案乘4,再加上坐标轴上的4个点,注意总点数是(2a+1)(2b+1)-1(原点不算在内)

第一象限的贡献为

s=i=1aj=1b[gcd(i,j)=1]

可以发现是一个典型莫反的式子,按照套路

s=i=1aj=1bd|gcd(i,j)μ(d)

考虑枚举d

s=d=1aμ(d)i=1adj=1bd1

不难发现

i=1adj=1bd1=adbd

于是

s=d=1aμ(d)adbd

ldr时,adbd的值不变,考虑将i=lrμ(i)一起计算

sum[d]=i=1dμ(i),则d=lrμ(d)=sum[r]sum[l1]可以O[1]得到

考虑数论分块,因为nd只有n种取值,所以在预处理出sum[i]的情况下,可以O(n)求出s

接下来考虑如何预处理sum

μ函数的定义可知其为积性函数,可以用欧拉筛O(n)筛出(杜教筛更优,但是我不会),那么就可以O(n)预处理出μ的前缀和sum

总复杂度为O(n+Qn),可以通过此题~

code
#include<bits/stdc++.h>
#define il inline
#define cs const
#define ri register
#define int long long
using namespace std;
namespace Q{
    il int rd(){
        ri int x=0,f=1;ri char c=getchar();
        while(c<'0'||c>'9') f=(c=='-')?-1:1,c=getchar();
        while(c>='0'&&c<='9') x=x*10+(c^48),c=getchar();
        return x*f; 
    }
    cs int N=2000;
    int mu[N+5],ss[N+5],cnt,sum[N+5];
    bool vs[N+5];
    il void init(){
        mu[1]=1,sum[1]=1;
        for(ri int i=2;i<=N;++i){
            if(!vs[i]) ss[++cnt]=i,mu[i]=-1;
            for(ri int j=1;j<=cnt&&ss[j]*i<=N;++j){
                vs[i*ss[j]]=1;
                if(i%ss[j]==0) break;
                mu[i*ss[j]]=-mu[i];
            }
            sum[i]=sum[i-1]+mu[i];
        }
    }
    il void done(int n,int m){
        if(n>m) swap(n,m);
        double as=0;
        for(ri int l=1,r=0;l<=n;l=r+1){
            r=min(n/(n/l),m/(m/l));
            as+=(sum[r]-sum[l-1])*(n/l)*(m/l);
        }
        as=as*4+4;
        as=as/((n*2+1)*(m*2+1)-1);
        printf("%.7lf\n",as);
        return;
    }
}
using namespace Q;
signed main(){
    init();
    int a=rd(),b=rd();
    while(a||b){
        done(a,b);
        a=rd(),b=rd();
    }	
    return 0;
} 

2.P2257 YY的GCD

原题链接


题目大意

i=1nj=1m[gcd(i,j)prime](n,m107)


分析

考虑枚举质数

s=pn,pprimei=1npj=1mp[gcd(i,j)=1]

这样就跟上一题差不多了

s=pn,pprimei=1npj=1mpd|gcd(i,j)μ(d)

枚举d

s=pn,pprimed=1npμ(d)i=1npdj=1mpd1=pn,pprimed=1npμ(d)npdmpd

一个常用套路,令T=pd,枚举T

s=T=1nnTmTp|T,pprimeμ(Tp)

发现p|T,pprimeμ(Tp)可以预处理

f(T)=p|T,pprimeμ(Tp)对于每一个质数p,枚举d,将f(dp)加上μ(d),第二维循环是一个调和级数,预处理总复杂度约为nlnn

预处理出f(T)的前缀和,然后就可以用数论分块O(n)求出s

总复杂度约为O(nlnn+Qn),可过~

psf(T)可以O(n)线性筛出来

f(ip)={μ(1)i=1μ(i)imodp=0f(i)+μ(i)imodp0

线性筛f(T)
il void init(int n){
    mu[1]=1;
    for(ri int i=2;i<=n;++i){
        if(!vs[i]) ss[++cnt]=i,mu[i]=-1,f[i]=1;
        for(ri int j=1;j<=tot&&ss[j]*i<=n;++j){
            vs[i*ss[j]]=1;
            if(i%ss[j]){
                f[i*ss[j]]=-f[i]+mu[i];
                mu[i*ss[j]]=-mu[i];
            }
            else{
                f[i*ss[j]]=mu[i];
                break;
            }
        }
        sum[i]=sum[i-1]+f[i];
    }
    return;
} 
code
#include<bits/stdc++.h>
#define il inline
#define cs const
#define ri register
using namespace std;
il int rd(int &x){
    x=0;ri int f=1;ri char c=getchar();
    while(c<'0'||c>'9') f=(c=='-')?-1:1,c=getchar();
    while(c>='0'&&c<='9') x=x*10+(c^48),c=getchar();
    return x*f;
}
il void wt(long long x){
    if(x<0) x=-x,putchar('-');
    ri unsigned short a[15]={0},tl=0;
    do{a[++tl]=x%10,x/=10;}while(x);
    for(ri int i=tl;i;--i) putchar(a[i]+48);
}
cs int N=1e7;
long long sum[N+5];
int ss[N+5],mu[N+5],cnt,f[N+5];
bool vs[N+5];
il void init(int n){
    mu[1]=1;
    for(ri unsigned int i=2;i<=n;++i){
        if(!vs[i]) ss[++cnt]=i,mu[i]=-1;
        for(ri unsigned int j=1;j<=cnt&&i*ss[j]<=n;++j){
            vs[i*ss[j]]=1;
            if(i%ss[j]==0) break;
            mu[i*ss[j]]=-mu[i];
        }
    }
    for(ri unsigned int j=1;j<=cnt;++j){
        for(ri int i=1;ss[j]*i<=n;++i){
            f[i*ss[j]]+=mu[i];
        }
    }
    for(ri unsigned int i=1;i<=n;++i){
        sum[i]=sum[i-1]+1ll*f[i];
    }
}
signed main(){
    int t=0,n=0,m=0;rd(t);
    ri long long as=0;init(N);
    while(t--){
        rd(n),rd(m),as=0;
        if(m<n) swap(m,n);
        for(ri unsigned int l=1,r=0;l<=n;l=r+1){
            r=min((n/l)?n/(n/l):n,(m/l)?m/(m/l):m);
            as+=1ll*(n/l)*(m/l)*(sum[r]-sum[l-1]);
        }
        wt(as),putchar('\n');
    }
    return 0;
}

3.P2398 GCD SUM

原题链接


题目大意

i=1nj=1ngcd(i,j)


分析

莫反做法

这题比上一题简单一些(那你为什么先写上一题)

考虑枚举gcd

s=i=1nj=1nd=1nd[gcd(i,j)=d]=d=1ndi=1nj=1n[gcd(i,j)=d]

将后面两项同时除以d,可得

s=d=1ndi=1ndj=1nd[gcd(i,j)=1]

一个标准的莫反式子

s=d=1ndi=1ndj=1ndg|gcd(i,j)μ(g)

枚举g

s=d=1ndg=1ndμ(g)i=1ndgj=1ndg1=d=1ndg=1ndμ(g)ndg2

对于

g=1ndμ(g)ndg2

可以线性筛预处理出μ(g)的前缀和,再进行数论分块,复杂度O(n)

总复杂度O(n+nn) ,在该题数据范围(n105)下可过

事实上可以继续往下推

考虑枚举T=dg

s=T=1nd|Tdμ(Td)nT2=T=1nd|Tid(d)μ(Td)nT2

根据狄利克雷卷积idμ=φ,得

s=T=1nφ(T)nT2

可以线性筛预处理φ(d)前缀和,然后数论分块,总复杂度O(n+n)


欧拉反演

可以直接用欧拉反演做,更好推~

欧拉函数有一个性质

d|nφ(d)=n

所以

gcd(i,j)=d|gcd(i,j)φ(d)

于是

s=i=1nj=1nd|gcd(i,j)φ(d)=d=1nφ(d)i=1n[i|d]j=1n[j|d]=d=1nφ(d)i=1ndj=1nd1=d=1nφ(d)nd2

跟上面推的式子是一样的


玄学递推

然而……还有更玄学简洁的做法……

s=k=1nkf(k)

f(k) 表示gcd(i,j)=k(i,j)对数

k|gcd(i,j)(i,j)对数为nk2

所以容斥一下

f(k)=nk2f(2k)f(3k)

复杂度 n(1+12+12++1n)O(nH(n))H(n)为调和级数,约等于lnn

因其常数较小,所以比欧拉反演还快……

code(莫反)
#include<bits/stdc++.h>
#define il inline
#define cs const
#define int long long
#define ri register
using namespace std;
cs int N=1e5+5;
int ss[N],vs[N],cnt,mu[N];
il void init(int n){
    mu[1]=1;
    for(ri int i=2;i<=n;++i){
        if(!vs[i]) ss[++cnt]=i,mu[i]=-1;
        for(ri int j=1;j<=cnt&&ss[j]*i<=n;++j){
            vs[i*ss[j]]=1;
            if(i%ss[j]==0) break;
            mu[i*ss[j]]=-mu[i];
        }
    }
    for(ri int i=2;i<=n;++i) mu[i]+=mu[i-1];
}
signed main(){
    int n,as=0;cin>>n;init(n);
    for(ri int d=1;d<=n;++d){
        int m=n/d;
        for(ri int l=1,r=0;l<=m;l=r+1){
            r=m/(m/l);
            as+=d*(mu[r]-mu[l-1])*(m/l)*(m/l);
        }
    }
    cout<<as;
    return 0;
}
code(欧拉)
//题解上粘的代码
#include <cstdio>
#include <cstring>

const int MAXN = 100010;

int prime[MAXN], v[MAXN], phi[MAXN], sumPhi[MAXN], cnt, n;

void eular(int n) {//线性筛筛欧拉函数
    memset(v, 0, sizeof(v));
    sumPhi[1] = phi[1] = 1;

    for (int i = 2; i <= n; i++) {
        if (!v[i]) {
            prime[++cnt] = v[i] = i;
            phi[i] = i - 1;
        }
        for (int j = 1; j <= cnt; j++) {
            if (prime[j] > v[i] || i * prime[j] > n) break;
            v[i * prime[j]] = prime[j];
            phi[i * prime[j]] = phi[i] * (i % prime[j] ? prime[j] - 1 : prime[j]);
        }
        sumPhi[i] = sumPhi[i - 1] + phi[i];//求欧拉函数的前缀和,如果整除分块的话就要用
    }
}

int main() {
    scanf("%d", &n);
    eular(n);

    long long ans = 0;
    for (int l = 1, r; l <= n; l = r + 1) {//这里是整除分块写法,如果不懂可以直接for 1 to n
        r = n / (n / l);
        ans += (long long) (sumPhi[r] - sumPhi[l - 1]) * (n / l) * (n / l);
    }
    printf("%lld\n", ans);
    return 0;
}
code(容斥)
#include<bits/stdc++.h>
#define ri register
long long n,ans,f[100010];
int main(){
    scanf("%lld",&n);
    for(ri int i=n;i;--i){
        f[i]=n/i*(n/i);
        for(ri int j=i<<1;j<=n;j+=i)f[i]-=f[j];
        ans+=f[i]*i;
    }
    printf("%lld",ans);
return 0;
}

4.P4450 双亲数

原题链接


题目大意

a=1Ab=1B[gcd(a,b)=d] (1A,B106,1dmin(A,B))


分析

将式子整体除以d

s=i=1Adj=1Bd[gcd(i,j)=1]

直接莫反

s=i=1Adj=1Bdg|gcd(i,j)μ(g)=g=1Adμ(g)i=1Adgj=1Bdg1=g=1Adμ(g)AdgBdg

线性筛加数论分块,O(n+n)

code
#include<bits/stdc++.h>
#define il inline
#define cs const
#define ri register
using namespace std;
il int rd(){
    ri int x=0,f=1;ri char c=getchar();
    while(c<'0'||c>'9') f=(c=='-')?-1:1,c=getchar();
    while(c>='0'&&c<='9') x=x*10+(c^48),c=getchar();
    return x*f;
}
il void wt(long long x){
    if(x<0) x=-x,putchar('-');
    ri unsigned short a[15]={0},tl=0;
    do{a[++tl]=x%10,x/=10;}while(x);
    for(ri int i=tl;i;--i) putchar(a[i]+48);
}
cs int N=1e6;
int ss[N+5],mu[N+5],cnt,sum[N+5]; 
bool vs[N+5];
il void init(int n){
    mu[1]=sum[1]=1;
    for(ri unsigned int i=2;i<=n;++i){
        if(!vs[i]) ss[++cnt]=i,mu[i]=-1;
        for(ri unsigned int j=1;j<=cnt&&i*ss[j]<=n;++j){
            vs[i*ss[j]]=1;
            if(i%ss[j]==0) break;
            mu[i*ss[j]]=-mu[i];
        }
        sum[i]=sum[i-1]+mu[i];
    }
}
signed main(){
    int n=0,m=0,d=0;
    ri long long as=0;init(N);
    n=rd(),m=rd(),d=rd(),as=0;
    n/=d,m/=d;
    if(m<n) swap(m,n);
    for(ri unsigned int l=1,r=0;l<=n;l=r+1){
        r=min(n/(n/l),m/(m/l));
        as+=1ll*(n/l)*(m/l)*(sum[r]-sum[l-1]);
    }
    wt(as);
    return 0;
}

5.P3455 [POI2007]ZAP-Queries

原题链接


题目大意

x=1ay=1b[gcd(x,y)=d] (1n5×104,1da,b5×104)


分析

跟上一题一样(双倍经验)

将式子整体除以d莫反可得

s=g=1adμ(g)adgbdg

线性筛加数论分块,O(n+Qn)

code
#include<bits/stdc++.h>
#define il inline
#define cs const
#define ri register
using namespace std;
il int rd(){
    ri int x=0,f=1;ri char c=getchar();
    while(c<'0'||c>'9') f=(c=='-')?-1:1,c=getchar();
    while(c>='0'&&c<='9') x=x*10+(c^48),c=getchar();
    return x*f;
}
il void wt(long long x){
    if(x<0) x=-x,putchar('-');
    ri unsigned short a[15]={0},tl=0;
    do{a[++tl]=x%10,x/=10;}while(x);
    for(ri int i=tl;i;--i) putchar(a[i]+48);
}
cs int N=5e4;
int ss[N+5],mu[N+5],cnt,sum[N+5]; 
bool vs[N+5];
il void init(int n){
    mu[1]=sum[1]=1;
    for(ri unsigned int i=2;i<=n;++i){
        if(!vs[i]) ss[++cnt]=i,mu[i]=-1;
        for(ri unsigned int j=1;j<=cnt&&i*ss[j]<=n;++j){
            vs[i*ss[j]]=1;
            if(i%ss[j]==0) break;
            mu[i*ss[j]]=-mu[i];
        }
        sum[i]=sum[i-1]+mu[i];
    }
}
signed main(){
    int t=rd(),n=0,m=0,d=0;
    ri long long as=0;init(N);
    while(t--){
        n=rd(),m=rd(),d=rd(),as=0;
        if(m<n) swap(m,n);
        for(ri unsigned int l=1,r=0;l<=n;l=r+1){
            r=min(n/(n/l),m/(m/l));
            as+=1ll*(n/l/d)*(m/l/d)*(sum[r]-sum[l-1]);
        }
        wt(as),putchar('\n');
    }
    return 0;
}

6.P2522 [HAOI2011]Problem b

原题链接


题目大意

x=aby=cd[gcd(x,y)=k] (1n,k5×104,1ab5×104,1cd5×104)


分析

(三倍经验)

发现此题比上一题多了下界要求,而上一题已经求了下界均为1的特殊情况

考虑下界不都为1

可以用类似二维前缀和的容斥思想

s(i,j)表示上界分别为i,j,下界均为1

那么

s=s(b,d)s(a1,d)s(b,c1)+s(a1,c1)

就很好求了

复杂度还是O(n+Qn),不过常数大一些

code
#include<bits/stdc++.h>
#define il inline
#define cs const
#define ri register
using namespace std;
il int rd(){
    ri int x=0,f=1;ri char c=getchar();
    while(c<'0'||c>'9') f=(c=='-')?-1:1,c=getchar();
    while(c>='0'&&c<='9') x=x*10+(c^48),c=getchar();
    return x*f;
}
il void wt(long long x){
    if(x<0) x=-x,putchar('-');
    ri unsigned short a[15]={0},tl=0;
    do{a[++tl]=x%10,x/=10;}while(x);
    for(ri int i=tl;i;--i) putchar(a[i]+48);
}
cs int N=5e4;
int ss[N+5],mu[N+5],cnt,sum[N+5]; 
bool vs[N+5];
il void init(int n){
    mu[1]=sum[1]=1;
    for(ri unsigned int i=2;i<=n;++i){
        if(!vs[i]) ss[++cnt]=i,mu[i]=-1;
        for(ri unsigned int j=1;j<=cnt&&i*ss[j]<=n;++j){
            vs[i*ss[j]]=1;
            if(i%ss[j]==0) break;
            mu[i*ss[j]]=-mu[i];
        }
        sum[i]=sum[i-1]+mu[i];
    }
}
il int done(int n,int m,int d){
    if(n>m) swap(n,m);
    ri int as=0;
    for(ri unsigned int l=1,r=0;l<=n;l=r+1){
        r=min(n/(n/l),m/(m/l));
        as+=1ll*(n/l/d)*(m/l/d)*(sum[r]-sum[l-1]);
    }	
    return as;
} 
signed main(){
    int t=rd(),a,b,c,d,k,as=0;init(N);
    while(t--){
        a=rd(),b=rd(),c=rd(),d=rd(),k=rd();
        as=done(b,d,k)-done(a-1,d,k)-done(b,c-1,k)+done(a-1,c-1,k);
        wt(as),putchar('\n');
    }
    return 0;
}

edit

posted @   雨夜风月  阅读(61)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示