bzoj3994 [SDOI2015]约数个数和

Description


设d(x)为x的约数个数,给定N、M,求 ni=1mj=1d(ij) ∑ i = 1 n ∑ j = 1 m d ( i j )

1<=N, M<=50000
1<=T<=50000

Solution


首先要知道一个结论:

d(nm)=i|nj|m[gcd(i,j)==1] d ( n m ) = ∑ i | n ∑ j | m [ g c d ( i , j ) == 1 ]

证明可以考虑nm质因数分解得到 nm=p1k1p2k2...pnkn n m = p 1 k 1 p 2 k 2 . . . p n k n ,约数个数就是 (ki+1) ∏ ( k i + 1 ) 。这里可以感性地看成是枚举其中某两段互质的

那么有

ans=i=1nj=1md(ij)=i=1nj=1mp|iq|j[gcd(p,q)==1] a n s = ∑ i = 1 n ∑ j = 1 m d ( i j ) = ∑ i = 1 n ∑ j = 1 m ∑ p | i ∑ q | j [ g c d ( p , q ) == 1 ]

回忆一下μ的性质有

ans=i=1nj=1mp|iq|jx|gcd(p,q)μ(x) a n s = ∑ i = 1 n ∑ j = 1 m ∑ p | i ∑ q | j ∑ x | g c d ( p , q ) μ ( x )

套路一波把x拉出来
ans=i=1nj=1mx|gcd(i,j)d(ix)d(jx)μ(x) a n s = ∑ i = 1 n ∑ j = 1 m ∑ x | g c d ( i , j ) d ( i x ) d ( j x ) μ ( x )

这里为什么是 d(ix) d ( i x ) 可以考虑转化之前p满足 x|p x | p p|i p | i ,那么就是求满足 xk|i x k | i 的k的数量,也即 k|ix k | i x 的数量
再套路一波继续把x拉出来
ans=x=1min(n,m)μ(x)i=1nxd(i)j=1mxd(j) a n s = ∑ x = 1 m i n ( n , m ) μ ( x ) ∑ i = 1 ⌊ n x ⌋ d ( i ) ∑ j = 1 ⌊ m x ⌋ d ( j )

可以发现d(x)是积性函数,预处理一波前缀和套分块就ok

Code


#include <stdio.h>
#include <string.h>
#include <algorithm>
#define rep(i,st,ed) for (int i=st;i<=ed;++i)

typedef long long LL;
const int N=50005;

int e[N+5],prime[N+5],d[N+5],mu[N+5];
bool not_prime[N+5];

void pre_work() {
    mu[1]=1; d[1]=1;
    rep(i,2,N-5) {
        if (!not_prime[i]) {
            prime[++prime[0]]=i;
            mu[i]=-1;
            d[i]=2;
            e[i]=1;
        }
        for (int j=1;j<=prime[0]&&i*prime[j]<=N;j++) {
            not_prime[i*prime[j]]=1;
            if (i%prime[j]==0) {
                mu[i*prime[j]]=0;
                d[i*prime[j]]=d[i]/(e[i]+1)*(e[i]+2);
                e[i*prime[j]]=e[i]+1;
                break;
            }
            mu[i*prime[j]]=-mu[i];
            d[i*prime[j]]=d[i]*d[prime[j]];
            e[i*prime[j]]=1;
        }
    }
    rep(i,1,N) d[i]+=d[i-1];
    rep(i,1,N) mu[i]+=mu[i-1];
}

void solve(int n,int m) {
    if (n>m) std:: swap(n,m);
    LL ans=0;
    for (int i=1,j;i<=n;i=j+1) {
        j=std:: min(n/(n/i),m/(m/i));
        ans+=(LL)((LL)mu[j]-(LL)mu[i-1])*(LL)((LL)d[n/i]*(LL)d[m/i]);
    }
    printf("%lld\n", ans);
}

int main(void) {
    pre_work();
    int T; scanf("%d",&T);
    while (T--) {
        int n,m; scanf("%d%d",&n,&m);
        solve(n,m);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值