BZOJ 3994 莫比乌斯反演

题目链接


题意:已知d(x)为x的约数个数,给定N,M(<=50000),求:
∑ i = 1 n ∑ j = 1 m d ( i j ) \sum_{i=1}^n \sum_{j=1}^md(ij) i=1nj=1md(ij)

思路:
直接利用公式:
d ( n m ) = ∑ i ∣ n ∑ j ∣ m [ g c d ( i , j ) = = 1 ] d(nm) = \sum_{i|n}\sum_{j|m}[gcd(i,j) == 1] d(nm)=injm[gcd(i,j)==1]
前缀和:
∑ i = 1 n ∑ j = 1 m d ( i j ) = ∑ i = 1 n ∑ j = 1 m ⌊ n i ⌋ ⌊ m j ⌋ [ g c d ( i , j ) = = 1 ] \sum_{i=1}^n \sum_{j=1}^md(ij) = \sum_{i=1}^n \sum_{j=1}^m\lfloor\frac{n}{i}\rfloor\lfloor\frac{m}{j}\rfloor[gcd(i,j) == 1] i=1nj=1md(ij)=i=1nj=1minjm[gcd(i,j)==1]

证明参考博客
什么二大重数学归纳,反正我是没看懂= =
不过直接考虑质因子的贡献还是比较好想通的。

这样的话,设答案为Ans,则:
A n s = ∑ i = 1 n ∑ j = 1 m ⌊ n i ⌋ ⌊ m j ⌋ [ g c d ( i , j ) = = 1 ] Ans = \sum_{i=1}^n \sum_{j=1}^m\lfloor\frac{n}{i}\rfloor\lfloor\frac{m}{j}\rfloor[gcd(i,j) == 1] Ans=i=1nj=1minjm[gcd(i,j)==1]
∑ d ∣ n μ ( d ) = [ n = = 1 ] \sum_{d|n}\mu(d) = [n == 1] dnμ(d)=[n==1],得:
A n s = ∑ i = 1 n ∑ j = 1 m ⌊ n i ⌋ ⌊ m j ⌋ ∑ d ∣ g c d ( i , j ) μ ( d ) Ans=\sum_{i=1}^n \sum_{j=1}^m\lfloor\frac{n}{i}\rfloor\lfloor\frac{m}{j}\rfloor \sum_{d|gcd(i,j)}\mu(d) Ans=i=1nj=1minjmdgcd(i,j)μ(d)
转换枚举变量,得:
A n s = ∑ d = 1 m i n ( n , m ) μ ( d ) ∑ i = 1 ⌊ n d ⌋ ⌊ n i d ⌋ ∑ j = 1 ⌊ m d ⌋ ⌊ m j d ⌋ Ans = \sum_{d=1}^{min(n,m)}\mu(d) \sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\lfloor\frac{n}{id}\rfloor\sum_{j=1}^{\lfloor\frac{m}{d}\rfloor}\lfloor\frac{m}{jd}\rfloor Ans=d=1min(n,m)μ(d)i=1dnidnj=1dmjdm

令: f ( x ) = ∑ i = 1 x ⌊ x i ⌋ f(x) = \sum_{i=1}^x\lfloor\frac{x}{i}\rfloor f(x)=i=1xix

得出:
A n s = ∑ d = 1 m i n ( n , m ) μ ( d ) f ( ⌊ n d ⌋ ) f ( ⌊ m d ⌋ ) Ans = \sum_{d=1}^{min(n,m)}\mu(d)f(\lfloor\frac{n} {d}\rfloor)f(\lfloor\frac{m}{d}\rfloor) Ans=d=1min(n,m)μ(d)f(dn)f(dm)

O ( n n ) O(n \sqrt n) O(nn )预处理 f ( x ) f(x) f(x)然后分块就好啦

代码:

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

后来看网上博客都说 f ( x ) f(x) f(x)显然是 d ( x ) d(x) d(x)的前缀。
推导了一下确实如此。
对于前n个约数个数之和:
∑ i = 1 n d ( i ) = ∑ i = 1 n ∑ j = 1 n [ j ∣ i ] = ∑ i = 1 n ∑ j = 1 n [ i ∣ j ] = ∑ i = 1 n ⌊ n i ⌋ \sum_{i=1}^nd(i) = \sum_{i=1}^n\sum_{j=1}^n[j|i] = \sum_{i=1}^{n}\sum_{j=1}^n[i|j] = \sum_{i=1}^n \lfloor\frac{n}{i}\rfloor i=1nd(i)=i=1nj=1n[ji]=i=1nj=1n[ij]=i=1nin

故可以优化到 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 = 5e4 + 10;
int pri[A],cnt[A],tot;
bool vis[A];
ll d[A],mu[A];
 
void init(){
    tot = 0;mu[1] = d[1] = 1;
    for(int i=2 ;i<A ;i++){
        if(!vis[i]){mu[i] = -1;pri[++tot] = i;d[i] = 2;cnt[i] = 1;}
        for(int j=1 ;j<=tot && pri[j]*i<A ;j++){
            vis[i*pri[j]] = 1;
            if(i%pri[j] == 0){
                d[i*pri[j]] = d[i]/(cnt[i]+1)*(cnt[i]+2);
                cnt[i*pri[j]] = cnt[i] + 1;
                mu[i*pri[j]] = 0;
                break;
            }
            d[i*pri[j]] = d[i]<<1;
            cnt[i*pri[j]] = 1;
            mu[i*pri[j]] = -mu[i];
        }
    }
    for(int i=1 ;i<A ;i++){
        mu[i] += mu[i-1];
        d[i]  += d[i-1];
    }
}
 
int main(){
    init();
    int T;
    scanf("%d",&T);
    while(T--){
        int n,m,last;
        scanf("%d%d",&n,&m);
        if(n>m) swap(n,m);
        ll ans = 0;
        for(int i=1 ;i<=n ;i=last+1){
            last = min(n/(n/i),m/(m/i));
            ans += 1LL*(mu[last] - mu[i-1])*d[n/i]*d[m/i];
        }
        printf("%lld\n",ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值