BZOJ 2820 莫比乌斯反演

来源:互联网 发布:软件激活码拿码平台 编辑:程序博客网 时间:2024/06/04 12:27

题目链接(权限题)

题意:给定n,m,问满足1<=x<=n and 1<=y<=mgcd(x,y)为素数的方案数有多少

思路:关注此题很久了,今天借了高爷的账号终于补掉了此题(再次表示感谢qwq)

我们试着来推导一下公式。
设:
f(i):gcd(x,y)=i 的方案数。
F(i):i|gcd(x,y) 的方案数。

易得:

F(n)=n|df(d)f(n)=n|dμ(dn)F(d)

又因为

F(d)=ndmd

得:

f(n)=n|dμ(dn)ndmd

此时我们可以枚举质数p,设答案为Ans,则:此处d是枚举p的倍数

Ans=pmin(n,m)dmin(n,m)μ(d)npdmpd

但这么做还是会T的,我们可以考虑进一步优化。

T=pd
此时我们转换一个思路,可不可以直接枚举T来进行优化复杂度。因为T的范围一定也是[1,min(n,m)]
故答案可写成:

Ans=Tmin(n,m)nTmTp|Tμ(Tp)

此时后面的约数莫比乌斯函数之和可以用预处理前缀和去维护。
这样我们就能在O(n)的复杂度得到答案。

但预处理时枚举素数需要O(n/n)而枚举倍数平摊下来是O(n)的复杂度。
故总复杂度:O(n)

此处还有一个小优化:可以在线性筛时同时处理约数莫比乌斯函数之和。可减少常数。

代码:

#include<cstdio>#include<cstring>#include<cstdlib>#include<cmath>#include<algorithm>using namespace std;typedef long long ll;const int A = 1e7 + 10;int pri[A],mu[A],tot,sum[A];bool vis[A];void init(){    tot = 0;    mu[1] = vis[1] = vis[0] = 1;    for(int i=2 ;i<A ;i++){        if(!vis[i]){pri[++tot] = i;mu[i] = -1;}        for(int j=1 ;j<=tot&&i*pri[j]<A ;j++){            vis[i*pri[j]] = 1;            if(i%pri[j] == 0){                mu[i*pri[j]] = 0;                break;            }            mu[i*pri[j]] = -mu[i];        }    }    for(int i=1 ;i<=tot ;i++){        for(int j=1 ;j*pri[i]<A ;j++){            sum[j*pri[i]] += mu[j];        }    }    for(int i=1 ;i<A ;i++) sum[i] += sum[i-1];}int main(){    init();    int T;    scanf("%d",&T);    while(T--){        int n,m;        scanf("%d%d",&n,&m);        int last;        ll ans = 0;        for(int i=1 ;i<=min(n,m) ;i=last+1){            last = min(n/(n/i),m/(m/i));            ans += 1LL*(n/i)*(m/i)*(sum[last]-sum[i-1]);        }        printf("%lld\n",ans);    }    return 0;}

(优化)

#include<cstdio>#include<cstring>#include<cstdlib>#include<cmath>#include<algorithm>using namespace std;typedef long long ll;const int A = 1e7 + 10;int pri[A],mu[A],tot,sum[A];bool vis[A];void init(){    tot = 0;    mu[1] = vis[1] = vis[0] = 1;    for(int i=2 ;i<A ;i++){        if(!vis[i]){pri[++tot] = i;mu[i] = -1;sum[i]=1;}        for(int j=1 ;j<=tot&&i*pri[j]<A ;j++){            vis[i*pri[j]] = 1;            if(i%pri[j] == 0){                mu[i*pri[j]] = 0;                sum[i*pri[j]] = mu[i];                break;            }            mu[i*pri[j]] = -mu[i];            sum[i*pri[j]] = mu[i] - sum[i];        }    }    for(int i=1 ;i<A ;i++) sum[i] += sum[i-1];}int main(){    init();    int T;    scanf("%d",&T);    while(T--){        int n,m;        scanf("%d%d",&n,&m);        int last;        ll ans = 0;        for(int i=1 ;i<=min(n,m) ;i=last+1){            last = min(n/(n/i),m/(m/i));            ans += 1LL*(n/i)*(m/i)*(sum[last]-sum[i-1]);        }        printf("%lld\n",ans);    }    return 0;}
原创粉丝点击