[SDOI2015][洛谷P3327]约数个数和(莫比乌斯反演+数论分块讲解)

题面

https://www.luogu.com.cn/problem/P3327

前置知识

  • 线性筛积性函数:https://www.cnblogs.com/zhoushuyu/p/8275530.html
  • 莫比乌斯反演:《具体数学:计算机科学基础》4.9节
  • 求和中化解gcd的小技巧: https://www.cnblogs.com/xh092113/p/12268356.html
  • 数论分块(接下来会介绍)

数论分块

用来在\(O(\sqrt{n})\)的时间内解决对\({\sum_{i=1}^{n}}f({\lfloor}{\frac{n}{i}}{\rfloor})\)求和或类似问题的一种技巧。

性质:\({\lfloor}{\frac{n}{i}}{\rfloor}(1{\leq}i{\leq}n)\) 的取值最多有\(O(\sqrt{n})\)个。

证:\(1{\leq}i{\leq}{\sqrt{n}}\)时,取值最多为i的个数\({\sqrt{n}}\)

\({\sqrt{n}}{\leq}i{\leq}n\)时,有\(1{\leq}{\lfloor}{\frac{n}{i}}{\rfloor}{\leq}{\sqrt{n}}\),又\({\lfloor}{\frac{n}{i}}{\rfloor}{\in}Z\),所以最多也是\({\sqrt{n}}\)

性质得证。

故如果将1~n这n个数,\({\lfloor}{\frac{n}{i}}{\rfloor}\)取值相同的合并为一段,最终我们会得到\(O({\sqrt{n}})\)段。

而如果我们知道了某一段的左端点L,就可以通过

\[R={\lfloor}{\frac{n}{{\lfloor}{\frac{n}{L}}{\rfloor}}}{\rfloor}\]

的方式,得到这一段的右端点R。之所以这个式子成立,是因为R是使得\(x*{{\lfloor}{\frac{n}{L}}{\rfloor}}{\leq}n\)的最大的x。

之后,再使L=R+1,就可以得到下一段的左端点。直到R=n终止。

这样,就可以用O(1)的时间对每一段求和,所以总的时间复杂度也就是段数\(O({\sqrt{n}})\)

题解

引理:\(d(ij) = {\sum_{p|i}}{\sum_{q|j}}[(p,q)=1]\)

证:设\(i={\prod_{i=1}^{k}}p_k^{\alpha_k},j={\prod_{i=1}^{k}}p_k^{\beta_k}\)。其中\(p_1{\leq}p_2{\leq}…{\leq}p_k\)为质数,\(max({\alpha_i},{\beta_i}){\geq}1\)

由公式可得\(d(ij)={\prod_{i=1}^{k}}({\alpha_i}+{\beta_i}+1)\)。(1)

另一方面,

\[{\sum_{p|i}}{\sum_{q|j}}[(p,q)=1]\]

\[={\sum\limits_{0{\leq}{\gamma_1}{\leq}{\alpha_1},0{\leq}{\gamma_2}{\leq}{\alpha_2},…,0{\leq}{\gamma_k}{\leq}{\alpha_k}}}{\ } {\sum\limits_{0{\leq}{\theta_1}{\leq}{\beta_1},0{\leq}{\theta_2}{\leq}{\beta_2},…,0{\leq}{\theta_k}{\leq}{\beta_k}}}[min({\gamma_1},{\theta_1})=0][min({\gamma_2},{\theta_2})=0]…[min({\gamma_k},{\theta_k})=0]\]

\[=({\sum\limits_{0{\leq}{\gamma_1}{\leq}{\alpha_1}}}{\ }{\sum\limits_{0{\leq}{\theta_1}{\leq}{\beta_1}}}[min({\gamma_1},{\theta_1})=0])*({\sum\limits_{0{\leq}{\gamma_2}{\leq}{\alpha_2}}}{\ }{\sum\limits_{0{\leq}{\theta_2}{\leq}{\beta_2}}}[min({\gamma_2},{\theta_2})=0])*…*({\sum\limits_{0{\leq}{\gamma_k}{\leq}{\alpha_k}}}{\ }{\sum\limits_{0{\leq}{\theta_k}{\leq}{\beta_k}}}[min({\gamma_k},{\theta_k})=0])\]

\[={\prod\limits_{i=1}^{k}}({\sum\limits_{0{\leq}{\gamma_i}{\leq}{\alpha_i}}}{\ }{\sum\limits_{0{\leq}{\theta_i}{\leq}{\beta_i}}}[min({\gamma_i},{\theta_i})=0])\]

对于\(0{\leq}{\gamma}{\leq}{\alpha},0{\leq}{\theta}{\leq}{\beta}\),要满足\(min({\gamma},{\theta})=0\)只能有\((0,1)、(0,2)…(0,{\beta})\)\((1,0)、(2,0)…(\alpha,0)\)以及\((0,0)\)\({\alpha}+{\beta}+1\)组解。

故上式\(={\prod\limits_{i=1}^{k}}(\alpha_i+\beta_i+1)\)=(1)式。引理得证。

回原题

由引理,

\[{\sum\limits_{i=1}^{n}}{\sum\limits_{j=1}^{m}}d(ij)\]

\[={\sum\limits_{i=1}^{n}}{\sum\limits_{j=1}^{m}}{\sum\limits_{p|i}}{\sum\limits_{q|j}}[(p,q)=1]\]

为化解gcd,我们使用技巧,此处\(f(x)=[x=1]=e(x),g(x)=f{\times}{\mu}={\mu}\)

则原式=

\[={\sum\limits_{i=1}^{n}}{\sum\limits_{j=1}^{m}}{\sum\limits_{p|i}}{\sum\limits_{q|j}}f((p,q))\]

\[={\sum\limits_{i=1}^{n}}{\sum\limits_{j=1}^{m}}{\sum\limits_{p|i}}{\sum\limits_{q|j}}{\sum\limits_{d|(p,q)}}g(d)\]

\[={\sum\limits_{i=1}^{n}}{\sum\limits_{j=1}^{m}}{\sum\limits_{p|i}}{\sum\limits_{q|j}}{\sum\limits_{d|p,d|q}}{\mu}(d)\]

\[={\sum\limits_d}{\mu(d)}{\sum\limits_{i=1}^{n}}{\sum\limits_{j=1}^{m}}{\sum\limits_{p=1}^{n}}{\sum\limits_{q=1}^{m}}[d|p][p|i][d|q][q|i]\]

\[={\sum\limits_d}{\mu(d)}({\sum\limits_{i=1}^{n}}{\sum\limits_{p=1}^{n}}[d|p][p|i])({\sum\limits_{j=1}^{m}}{\sum\limits_{q=1}^{m}}[d|q][q|i])\]

\[={\sum\limits_d}{\mu(d)}({\sum\limits_{p=1}^{n}}[d|p]{\lfloor}{\frac{n}{p}}{\rfloor})({\sum\limits_{q=1}^{m}}[d|q]{\lfloor}{\frac{m}{q}}{\rfloor})\]

\[={\sum\limits_d}{\mu(d)}({\sum\limits_{p'=1}^{{\lfloor}{\frac{n}{d}}{\rfloor}}}{\lfloor}{\frac{n}{p'd}}{\rfloor})({\sum\limits_{q'=1}^{{\lfloor}{\frac{m}{d}}{\rfloor}}}{\lfloor}{\frac{m}{q'd}}{\rfloor})\]

\[={\sum\limits_d}{\mu(d)}({\sum\limits_{p'=1}^{{\lfloor}{\frac{n}{d}}{\rfloor}}}{\lfloor}{\frac{{\lfloor}{\frac{n}{d}}{\rfloor}}{p'}}{\rfloor})({\sum\limits_{q'=1}^{{\lfloor}{\frac{m}{d}}{\rfloor}}}{\lfloor}{\frac{{\lfloor}{\frac{m}{d}}{\rfloor}}{q'}}{\rfloor})\]

进而,我们观察后两个括号内的数,其实就是\(h({\lfloor}{\frac{n}{d}}{\rfloor})\)\(h({\lfloor}{\frac{m}{d}}{\rfloor})\),其中h(n)是为1~n的约数个数和,也就是\({\sum_{i=1}^{n}}d(i)\)。(这里的d不是前面推式子的时候的变量,而是题目中就已定义的那个函数)

过程中,之所以\({\sum_{i=1}^{n}}d(i)={\sum_{i=1}^{n}}{\lfloor}{\frac{n}{i}}{\rfloor}\),是因为

\[{\sum\limits_{i=1}^{n}}d(i)={\sum\limits_{j=1}^{n}}{\sum\limits_{i=1}^{n}}[j|i]={\sum\limits_{j=1}^{n}}{\lfloor}{\frac{n}{j}}{\rfloor}\]

我们可以用埃筛预处理出所有的d(i),然后求前缀和得到h(i)。

目前的时间复杂度是O(Tn),不能满足要求。

注意到h的形式满足数论分块的要求,故此处可以用数论分块进行优化。

但是需要注意的是,这里要对数论分块进行一个小拓展,即\({\lfloor}{\frac{n}{d}}{\rfloor}\)\({\lfloor}{\frac{m}{d}}{\rfloor}\)变化的时候都需要分段,故这里应该分成最多\(O({\sqrt{n}}+{\sqrt{m}})\)段,求右端点的时候也应该从本来的

R = n / (n / L);

拓展为

R = min(n / (n/L),m / (m/L));

分块结束后,同一段的一起处理,只需要先行预处理\({\mu(i)}\)的前缀和即可O(1)统计一段。

至此总时间复杂度为\(O(T{\sqrt{n}})\)

代码

#include

using namespace std;

#define N 50000
#define rg register
#define ll long long

inline ll read(){
    ll s = 0,ww = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-')ww = -1;ch = getchar();}
    while('0' <= ch && ch <= '9'){s = 10 * s + ch - '0';ch = getchar();}
    return s * ww;
}

inline void write(ll x){
    if(x < 0)putchar('-'),x = -x;
    if(x > 9)write(x / 10);
    putchar('0' + x % 10);
}

ll pn;
ll pri[10000+5],mu[N+5],sm[N+5]; //sm是mu的前缀和 
bool isp[N+5];

inline void Eular(){  
    pn = 0;
    mu[1] = 1;
    for(rg ll i = 2;i <= N;i++)isp[i] = 1;
    for(rg ll i = 2;i <= N;i++){
        if(isp[i])pri[++pn] = i,mu[i] = -1;
        for(rg ll j = 1;i * pri[j] <= N;j++){
            isp[i*pri[j]] = 0;
            if(i % pri[j])mu[i*pri[j]] = -mu[i];
            else{
                mu[i*pri[j]] = 0;
                break;  
            }
        }
    }
} 

ll d[N+5],h[N+5];

inline void prepro(){ //预处理d[]和h[](h即d的前缀和) 
    for(rg ll i = 1;i <= N;i++){
        for(rg ll j = i;j <= N;j += i)d[j]++;
    } 
    for(rg ll i = 1;i <= N;i++)h[i] = h[i-1] + d[i];
}

int main(){
    Eular();
    for(rg ll i = 1;i <= N;i++)sm[i] = sm[i-1] + mu[i];
    prepro();
    ll T = read();
    while(T--){
        ll n = read(),m = read();
        ll limit = min(n,m);
        ll ans = 0;
        ll L = 1,R = min(n / (n/L),m / (m/L));
        while(1){
            ans += (sm[R] - sm[L-1]) * h[n/L] * h[m/L];
            if(R == limit)break;
            L = R + 1,R = min(n / (n/L),m / (m/L));
        }
        write(ans);putchar('\n');
    }
    return 0;
}

你可能感兴趣的:([SDOI2015][洛谷P3327]约数个数和(莫比乌斯反演+数论分块讲解))