[SDOI2015]约数个数和(莫比乌斯反演)

题意

d(x)表示x的约数个数,求T\sum_{i=1}^{N} \sum_{j=1}^{M} d(ij)

数据范围:1\leq T,N,M \leq 50000

题解

这个题目的关键是化解d(ij),这里有个神奇的公式d(xy)=\sum_{i|x}\sum_{j|y} [gcd(i,j)==1](第一次了解到我是懵逼的 ( ′◔ ‸◔`) )

这个公式可以这么理解: 如果对于x的素因子分解为\prod p_{i}^{a_i},那么d(x)=\prod (a_i +1),可以看出每个素因子p_i作用是独立的

假设x,y的素因子分解中p_1的次数为分别为a,bp_1对于d(xy)的贡献为(a+b+1)

d(xy)=\sum_{i|x}\sum_{j|y} [gcd(i,j)==1]中只考虑p_1

gcd(i,j)==1只当p_1i中出现j中不出现的a种, i中不出现j中出现的b种, 都不出现的1种,所以贡献为(a+b+1),等式成立

 

那么原来要求的式子变为 \sum_{i=1}^{N} \sum_{j=1}^{M} \sum_{x|i} \sum_{y|j} [gcd(x,y)==1],这里可以看到x,y可以转换为约数的贡献

1-N,M中约数作为枚举项, 式子变为 \sum_{i=1}^N \sum_{j=1}^M [\frac{N}{i}] [\frac{M}{j}] [gcd(i,j)==1](i,j相当于上式x,y,不过是整体考虑每个x,y贡献)

对于[gcd(i,j)==1]这个式子如果做过相关题目那就很熟悉了求两两gcd和的不同思路

 

f(d)=\sum\sum [\frac{N}{i}] [\frac{M}{j}] [gcd(i,j)==d], \ F[d]=\sum\sum [\frac{N}{i}] [\frac{M}{j}] [d|gcd(i,j)](F(n)条件比f(n)更松,也更易求)

就有F(d)=\sum_{d|n} f(n),由莫比乌斯反演f(d)=\sum_{d|n} \mu (\frac{n}{d}) F(n)

那么问题就变为如何求解F(n)注意到对于F(n),如果d|i,d|j那么就有贡献

考虑d的约数d,2d,...,xd.以1,2,...,x作为枚举项,有F(d)=\sum_{x=1}^{[\frac{N}{d}]}\sum_{y=1}^{[\frac{M}{d}]} [\frac{N}{xd}] [\frac{M}{yd}](这里x,y独立,可以通过处理前缀和O(1)求出)

f(1)=\sum_{1|n} \mu (\frac{n}{1}) F(n)=\sum_{d=1}^{min(N,M)} \sum_{x=1}^{[\frac{N}{d}]} [\frac{N}{xd}] \sum_{y=1}^{[\frac{M}{d}]} [\frac{M}{yd}]

 

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;

using LL = long long;
const int MAXN = 5e4 + 5;
int T, N, M;
bool noprime[MAXN];
int prime[MAXN], cnt_p, mu[MAXN];
int pre[MAXN], sum[MAXN];
void Euler_Sieve(int top);

int main(){
  ios::sync_with_stdio(false);
  Euler_Sieve(MAXN - 5);
  cin >> T;
  while(T--){
    cin >> N >> M;
    int l, r, top = min(N, M); LL ans = 0;
    for(l = 1; l <= top; l = r + 1){
      r = min(N / (N / l), M / (M / l));
      ans += 1LL * (pre[r] - pre[l - 1]) * sum[N / l] * sum[M / l];
    }
    cout << ans << endl;
  }
  return 0;
}

void Euler_Sieve(int top){
  int i, j;
  mu[1] = 1;
  for(i = 2; i <= top; i++){
    if(!noprime[i]) prime[++cnt_p] = i, mu[i] = -1;
    for(j = 1; j <= cnt_p && prime[j] * i <= top; j++){
      noprime[prime[j] * i] = true;
      if(i % prime[j] == 0) break;
      mu[prime[j] * i] = -mu[i];
    }
  }

  for(i = 1; i <= top; i++) pre[i] = pre[i - 1] + mu[i];

  int l, r;
  for(i = 1; i <= top; i++){
    for(l = 1; l <= i; l = r + 1){
      r = i / (i / l);
      sum[i] += 1LL * (i / l) * (r - l + 1);
    }
  }
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值