洛谷P3312 [SDOI2014]数表 莫比乌斯反演+线段树+约数和筛


洛谷P3327 [SDOI2015]约数个数和
****
### 标签

  • 莫比乌斯反演
  • 整除分块
  • 线性筛
    ****
    ### 前言
  • 这里的整除分块是另一种常见的形式。我半天都没搞清楚是怎么分的..好久之后才恍然大悟
    ****
    ### 简明题意
  • $d(x)$表示$x$的约数个数。给定$n,m$,求
    $$\sum_{i=1}^n\sum_{j=1}^md(ij)$$
    ***
    ### 思路
  • 首先大家应该知道这样一个很常用的式子:
    $$d(i*j)=\sum_{x|i}\sum_{y|j}[gcd(x,y)==1]$$

    这个式子记住就好

  • 我们用这个式子计算$d(i,j)$,得出原式等于:
    $$\sum_{i=1}^n\sum_{j=1}^md(ij)=\sum_{i=1}^n\sum_{j=1}^m\sum_{x|i}\sum_{y|j}[gcd(x,y)==1]$$
  • $x,y$是$i,j$的因数,暴力计算因数复杂度太高了,我们去枚举$x,y$,显然$n,m$的约数分别最大不超过$n,m$,因此,原式变成:
    $$\sum_{x=1}^n\sum_{y=1}^m\left([\frac nx][\frac my][gcd(x,y)==1]\right)$$
  • 对于$[gcd(x,y)==1]$我们很快能想到用莫比乌斯函数性质替换成$\sum\limits_{d|gcd(i,j)}\mu(d)$,而又有这样的性质:$d|gcd(i,j)\iff d|i 且d|j$ 于是就成了:
    $$\sum_{i=1}^n\sum_{j=1}^m\left([\frac ni][\frac mj]\sum\limits_{d|i且d|j}\mu(d)\right)$$
  • 换了之后就可以改为枚举$d$,$d$作为$n$的约数,显然$d$的上限是$n$。就成了:
    $$\sum_{d=1}^n\mu(d)\sum_{i=1}^n\sum_{j=1}^m\left([d|i且d|j][\frac ni][\frac mj] \right)$$
  • 又有
    $$\sum\limits_{i=1}^n\sum\limits_{j=1}^m\left([d|i且d|j][\frac ni][\frac mj]\right)=\sum\limits_{i=1}^{[\frac nd]}\sum\limits_{j=1}^{[\frac md]}[\frac n{id}]*[\frac m{jd}]$$

    推导:我们可以发现,$\sum\limits_{i=1}^n\sum\limits_{j=1}^m[d|i且d|j]$,实际有效的二元组$(i,j)$,跟$\sum\limits_{i=1}^{[\frac nd]}\sum\limits_{j=1}^{[\frac md]}$所枚举的二元组$(i,j)$在数量上是一样多的。在数值上,前者二元组的大小是后者的$d$倍。因此,变换上限后,$i,j$成为了原来的$\frac 1d$,我们给它们乘上$d$就回到原来的项了。

  • 接下来,原式就会变成:
    $$\sum_{d=1}^n\mu(d)\sum\limits_{i=1}^{[\frac nd]}\sum\limits_{j=1}^{[\frac md]}[\frac n{id}]*[\frac m{jd}]$$
  • 这个时候,对于后面的式子$\sum\limits_{i=1}^{[\frac nd]}\sum\limits_{j=1}^{[\frac md]}[\frac n{id}]*[\frac m{jd}]$显然是可以通过移项改成两项相乘:$\sum\limits_{i=1}^{[\frac nd]}[\frac n{id}]\sum\limits_{j=1}^{[\frac md]}[\frac m{jd}]$,然后原式就成了:
    $$\sum_{d=1}^n\mu(d)\sum\limits_{i=1}^{[\frac nd]}[\frac n{id}]\sum\limits_{j=1}^{[\frac md]}[\frac m{jd}]$$
  • 这里观察$\sum\limits_{i=1}^{[\frac nd]}[\frac n{id}]\sum\limits_{j=1}^{[\frac md]}[\frac m{jd}]$是可以预处理的。这里有一个小难点,就是这里未知的既有$d$,又有$n,m$,预处理需要枚举$d,n,m$,那复杂度岂不是$O(n^3)$了?大家应该摒弃这种观念,思维不能定势。首先我们并不需要直接对$\sum\limits_{i=1}^{[\frac nd]}[\frac n{id}]\sum\limits_{j=1}^{[\frac md]}[\frac m{jd}]$这整个式子预处理,可以分开对$\sum\limits_{i=1}^{[\frac nd]}[\frac n{id}]$和$\sum\limits_{j=1}^{[\frac md]}[\frac m{jd}]$预处理,所以现在枚举的又可以减少为$d,m$两种了,复杂度$O(n^2)$。但是现在注意观察,$nd$在整个式子里都是一个整体,他们整体取值的范围是确定的。因此可以直接枚举$\frac nd$,这样预处理复杂度就是$O(n\sqrt n)$,总复杂度就是:$O(n\sqrt n+n*T)$。
  • 但是,这里有复杂度更低的方法,我们令$f(x)=\sum\limits_{i=1}^x[\frac xi]$,然后原式就变成:
    $$\sum_{d=1}^n\left(\mu(d)f([\frac nd])f([\frac md])\right)$$
  • $f$的任意项是可以通过前面所说的预处理出来,从而$O(1)$查询。但是,这里注意到$[\frac nd]$和$[\frac md]$是可以分块的,也就是对于一块$[l,r]$,这个区间的$[\frac nd]$和$[\frac md]$是确定的,也就是说这个区间的$f([\frac nd])f([\frac md])$是确定的。所以只需要用这个区间的$f([\frac nd])f([\frac md])$乘以$\mu(d)$区间和就可以了,最终复杂度降低为$O(n\sqrt n+\sqrt{n}*T)$,可以通过这一题
  • 其实还可以再做一个小优化。之前我们定义了$f(x)=\sum\limits_{i=1}^x[\frac xi]$,如果数论比较好的同学可以立马发现,$f(x)$就是$[1,x]$的约数个数之和,所以我们实际上可以线筛预处理出$d$函数,然后做一遍前缀和。这样优化大概会快4倍
    ****
    ### 注意事项
    ****
    ### 总结
  • 对于式子$\sum\limits_{i=1}^n\sum\limits_{j=1}^m[d|i且d|j]$很显然它等于$\sum\limits_{i=1}^{[\frac nd]}\sum\limits_{j=1}^{[\frac md]}1=[\frac nd][\frac md]$,而对于$\sum\limits_{i=1}^n\sum\limits_{j=1}^m[d|i且d|j][\frac ni][\frac mj]$,实际上,将枚举上限分别换成$[\frac nd]$和$[\frac md]$,我们枚举的就是所有$[d|i且d|j]$的二元组$[i,j]$的$\frac 1d$倍。然后我们计算$[\frac ni][\frac mj]$的时候要将$ij$放大$d$倍,因此实际上我们计算的就应该是$[\frac n{id}][\frac m{jd}]$,所以就有:
    $$\sum\limits_{i=1}^n\sum\limits_{j=1}^m[d|i且d|j]
    [\frac ni][\frac mj]=\sum\limits_{i=1}^{[\frac nd]}\sum\limits_{j=1}^{[\frac md]}[\frac n{id}][\frac m{jd}]$$
  • 对于这样的式子
    $$\sum_{i=1}^n\sum_{j=1}^mij=\left(\sum_{i=1}^ni\right)*\left(\sum_{j=1}^mj\right)$$这样可以直接将$O(n^2)$的复杂度降低为$O(n)$
  • 整除分块,最经典的是处理$\sum\limits_{i=1}^n[\frac xi]$。但有另一种形式也是常用的:$\sum\limits_{i=1}^nf([\frac xi])$,同样每一块的$[\frac ni]$是相同的,就可以对相同$k$个,算一遍$k*f[\frac xi]$
  • 对$d$函数的线性筛,需要多开一个数组$num[i]$记录$i$的最小质因子出现的次数。处理时,如果$i%prime[j]==0$,也就是含有最小值因子$prime[j]$,$num[i]$就应该更新成$num[i]+1$,否则的话,说明$i$里面没有质因子$prime[j]$,而$prime[j]$是新的最小质因子,因此num[i*prime[j]]=1(这里容易出错,要谨记!!!)
    ****
    ### AC代码
#include<cstdio>
#include<algorithm>
using namespace std;

const int maxn = 50000 + 10;

bool no_prime[maxn];
int prime[maxn], mu[maxn], pre_mu[maxn], dd[maxn], num[maxn];
long long pre_dd[maxn];
int shai(int n)
{
   int cnt = 0;
   mu[1] = dd[1] = 1;

   for (int i = 2; i <= n; i++)
   {
       if (!no_prime[i])
           prime[++cnt] = i, mu[i] = -1, dd[i] = 2, num[i] = 1;

       for (int j = 1; j <= cnt && prime[j] * i <= n; j++)
       {
           no_prime[prime[j] * i] = 1;
           mu[prime[j] * i] = (i % prime[j] == 0) ? 0 : -mu[i];
           dd[prime[j] * i] = (i % prime[j] == 0) ? dd[i] / (num[i] + 1) * (num[i] + 2) : dd[i] * 2;
           num[prime[j] * i] = (i % prime[j] == 0) ? num[i] + 1 : 1;
           if (i % prime[j] == 0) break;
       }
   }

   for (int i = 1; i <= n; i++)
       pre_mu[i] = pre_mu[i - 1] + mu[i], pre_dd[i] = pre_dd[i - 1] + dd[i];
       
   return cnt;
}

long long cal2(int n, int m)
{
   int l = 1, r;
   long long ans = 0;
   while (l <= n)
   {
       r = min(n / (n / l), m / (m / l));
       ans += 1ll * (pre_mu[r] - pre_mu[l - 1]) * pre_dd[n / l] * pre_dd[m / l];
       l = r + 1;
   }
   return ans;
}

void solve()
{
   shai(maxn - 10);

   int t;
   scanf("%d", &t);
   while (t--)
   {
       int n, m;
       scanf("%d%d", &n, &m);
       if (n > m) swap(n, m);

       printf("%lld\n", cal2(n, m));
   }
}

int main()
{
   solve();
   return 0;
}

猜你喜欢

转载自www.cnblogs.com/dzzzh/p/11294710.html
今日推荐