BZOJ 2820 莫比乌斯反演

题目链接(权限题)

题意:给定 n , m n,m n,m,问满足 1 &lt; = x &lt; = n   a n d   1 &lt; = y &lt; = m 1&lt;=x&lt;=n \space and \space 1&lt;=y&lt;=m 1<=x<=n and 1<=y<=m g c d ( x , y ) gcd(x,y) gcd(x,y)为素数的方案数有多少

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

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

易得:
F ( n ) = ∑ n ∣ d f ( d ) ⇒ f ( n ) = ∑ n ∣ d μ ( d n ) F ( d ) F(n) = \sum_{n|d}f(d) ⇒ f(n) = \sum_{n|d} \mu(\frac{d}{n})F(d) F(n)=ndf(d)f(n)=ndμ(nd)F(d)

又因为
F ( d ) = ⌊ n d ⌋ ⌊ m d ⌋ F(d) = \lfloor\frac{n}{d}\rfloor \lfloor\frac{m}{d}\rfloor F(d)=dndm

得:
f ( n ) = ∑ n ∣ d μ ( d n ) ⌊ n d ⌋ ⌊ m d ⌋ f(n) = \sum_{n|d} \mu(\frac{d}{n})\lfloor\frac{n}{d}\rfloor \lfloor\frac{m}{d}\rfloor f(n)=ndμ(nd)dndm

此时我们可以枚举质数 p p p,设答案为 A n s Ans Ans,则:此处 d d d是枚举 p p p的倍数
A n s = ∑ p m i n ( n , m ) ∑ d m i n ( n , m ) μ ( d ) ⌊ n p d ⌋ ⌊ m p d ⌋ Ans = \sum_{p}^{min(n,m)} \sum_{d}^{min(n,m)} \mu(d)\lfloor\frac{n}{pd}\rfloor \lfloor\frac{m}{pd}\rfloor Ans=pmin(n,m)dmin(n,m)μ(d)pdnpdm

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

T = p d T = pd T=pd
此时我们转换一个思路,可不可以直接枚举 T T T来进行优化复杂度。因为 T T T的范围一定也是 [ 1 , m i n ( n , m ) ] [1,min(n,m)] [1,min(n,m)]
故答案可写成:
A n s = ∑ T m i n ( n , m ) ⌊ n T ⌋ ⌊ m T ⌋ ∑ p ∣ T μ ( T p ) Ans = \sum_{T}^{min(n,m)}\lfloor\frac{n}{T}\rfloor \lfloor\frac{m}{T}\rfloor \sum_{p|T}\mu(\frac{T}{p}) Ans=Tmin(n,m)TnTmpTμ(pT)

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

但预处理时枚举素数需要 O ( n / n ) O(n/\sqrt n) O(n/n )而枚举倍数平摊下来是 O ( n ) O(\sqrt n) O(n )的复杂度。
故总复杂度: O ( 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值