嘘~ 正在从服务器偷取页面 . . .

数论分块 学习笔记


数论分块 学习笔记

设函数 $f,g : \mathbb{N} \to \mathbb{N}$ ,并且

  • 前缀和 $S_r-S_{l-1}$ 可以在 $\mathcal{O}(1)$ 计算,其中 $S_i = \sum_{j=1}^{i} f(j),~S_0 = 0$ 。
  • $g(i)$ 形如 $\left\lfloor\dfrac{n}{i}\right\rfloor$ ,有时候也可以形如 $\left\lfloor\dfrac{m}{ki+b}\right\rfloor$ ,总之取值具有阶梯状分布的特点。

数论分块可以在 $\mathcal{O}(\sqrt{n})$ 的复杂度内计算形如 $\sum_{i=1}^n f(i) g(i)$ 的和式。


数学原理

证明详见参考文献[1],以及这里一段可以直接跳过,见算法流程

引理1:

引理2:

数论分块结论

对于常数 $n$,使得 $\left\lfloor\frac ni\right\rfloor=\left\lfloor\frac nj\right\rfloor$ 成立的最大的满足 $i\leq j\leq n$ 的 $j$ 的值为 $\left\lfloor\dfrac n{\lfloor\frac ni\rfloor}\right\rfloor$。

即值 $\left\lfloor\dfrac ni\right\rfloor$ 所在的块的右端点为 $\left\lfloor\dfrac n{\lfloor\frac ni\rfloor}\right\rfloor$ ,并且根据引理2可知块数不超过 $\mathcal{O}(\sqrt{n})$ 。


算法讲解

我并不想像其他人的文章一样直接抛出算法流程,我更想用一种通俗解释来描述它。

刚刚的引理1就是个辅助的结论,引理2和主要结论是最重要的。

观察 $g(i) = \left\lfloor\frac{n}{ki}\right\rfloor$ 的图像,可以发现它呈阶梯状分布

举个例子,$g(x) = \left\lfloor\frac{10}{x}\right\rfloor$ 的图像如下图:(负半轴不合法,无视即可)

显然它呈阶梯状分布。而数论分块做的就是快速统计每个块并计算贡献。

而块的数量,由引理2可知为 $\mathcal{O}(\sqrt{n})$ ,并且主要结论做到了快速统计,因此复杂度是 $\mathcal{O}(\sqrt{n})$ 的。


算法流程

算法流程:


代码实现

模板题:洛谷P3935 Calculating

答案要求计算形如 $F(r) - F(l-1)$ 的式子(具体推导详见 洛谷P3935 Calculating 题解

首先这里的 $S_i = i$ ,因为 $f(i) = 1$ 。

模板代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
const int mod = 998244353;
void up(int &x,int y) { x < y ? x = y : 0; }
void down(int &x,int y) { x > y ? x = y : 0; }
void add(int &x,int y) { (x += y) >= mod ? x -= mod : 0; }
#define N ((int)())

int solve(int n)
{
    int res = 0;
    for(int l = 1, r = 0; l <= n; l = r + 1) // 注意这里不是 l++ 
    {
        r = n / (n / l); // 右端点
        // 增量为 [S(r) - S(l-1)] * (n / l) , 后面的是 n / l ,这是算法规定的。
        add(res, (r - l + 1) * (n / l) % mod);
    }
    return res;
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    int L,R; cin >> L >> R;
    cout << (solve(R) - solve(L - 1) + mod) % mod << endl;
    return 0;
}

例题

方便起见,这里指讲怎么算最后的答案,推导详见对应的题解。

例题1:来自 洛谷P2261 [CQOI2007]余数求和 题解

计算下列式子:

数据范围:$1 \le n,k \le 10^9$ 。

分析:这个就很板子,主要是这个前缀和。

根据等差数列的性质不难发现 $S_r - S_{l-1}$ 就是 $(r-l + 1) \times \dfrac{l + r}{2}$ 。

注意当 $k \le n$ 时只要枚举到 $k$ ,因为后面 $\left\lfloor\frac{k}{i}\right\rfloor$ 全为 $0$ ,所以不需要枚举(而且枚举后代码会出错)

时间复杂度 $\mathcal{O}(\sqrt{n})$

代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
int n,k;
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    cin >> n >> k;int res=n*k;
    for(int l=1,r; l<=min(k,n); l=r+1)
    {
        r=min(k/(k/l),n);
        res-=(k/l)*(r-l+1)*(l+r)/2;
    }
    cout << res << endl;
    return 0;
}


例题2:来自 CF1485C Floor and Mod 题解

多组询问,计算下列式子

数据范围:$1\le Q \le 100,~1 \leq x, y \leq 10^9$ 。

分析

遗憾的是,数论分块不能直接算这样的 $b+1$ 。

咋办,换元(改写以下式子)呗

好啦,没啦。只要令 $l=2$ 跑就好啦。

不过这道题是分类讨论(有点根号分治的味道),所以建议直接查看题解。

时间复杂度 $\mathcal{O}(Q\sqrt{x})$

#define int long long
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    int Q,a,b,x,y,res;
    cin >> Q;
    while(Q--)
    {
        cin >> x >> y;
        a=1,b=1,res=0;
        for(; b*b+b-1<x&&b<=y; b++) // 这里不用管,这是原题分类讨论的地方
            res+=(b*b+b-1)/(b+1);
        for(int l=b+1,r; l<=min(x,y+1); l=r+1) // 其实主要就是改了下式子(这里代码可读性很差)
        {
            r=min(x/(x/l),y+1);
            res+=x/l*(r-l+1); // 你会发现这里没啥区别
        }
        cout << res << '\n';
    }
    return 0;
}


相信你已经对数论分块有初步的了解了

例题3:来自 洛谷P3327 [SDOI2015]约数个数和 题解

计算下列式子

数据范围:多组数据,$1\le T,n,m \le 50000$ 。

分析

这个式子和板子差距有亿点大,但是写法还是类似的

注意到这里的 $dx,dy$ 其实你把它的 $d$ 放上去就是可以算的 $\left\lfloor\frac{\left\lfloor\frac{n}{d}\right\rfloor}{x}\right\rfloor$ 了

可以预处理一下这个东西为 $g\left(\left\lfloor\frac{n}{d}\right\rfloor\right)$ ,然后每组询问查表就好了。

感觉这么说比较抽象,直接看详细题解可能会比较好(大雾

接下来假设已经知道 $g$ 的所有值了,然后怎么做呢?

对于前面的那个求和,因为后面的那个 $g$ 也构成了阶梯状的值,很好,我们继续数论分块。

时间复杂度 $\mathcal{O}(T\sqrt{n} + n\sqrt{n})$

代码:(早期代码+注释+删掉了一些不影响阅读的东西)

#define int long long
#define N (int)(5e4+15)
int prime[N],sum[N],pcnt,mu[N],g[N];
bool ck[N];
int solve(int n)
{
    int res=0;
    for(int l=1,r; l<=n; l=r+1)
    {
        r=(n/(n/l));
        res+=(r-l+1)*(n/l);
    }
    return res;
}
void Mobius() // 筛法求 μ(n)
{
    mu[1]=1;
    for(int i=2; i<=N-5; i++)
    {
        if(!ck[i])
        {
            prime[++pcnt]=i;
            mu[i]=-1;
        }
        for(int j=1; j<=pcnt&&i*prime[j]<=N-5; j++)
        {
            int pos=i*prime[j];
            ck[pos]=1;
            if(i%prime[j])
            {
                mu[pos]=-mu[i];
            }else
            {
                mu[pos]=0;
                break;
            }
        }
    }
    for(int i=1; i<=N-5; i++) sum[i]+=sum[i-1]+mu[i]; // 做了一个 μ 的前缀和
    for(int i=1; i<=N-5; i++) g[i]=solve(i); // 预处理了模板题的那个式子
}
int Q,n,m;
signed main()
{
    Mobius();
    read(Q);
    while(Q--)
    {
        read(n);read(m);
        if(n>m)swap(n,m);
        int res=0;
        for(int l=1,r; l<=n; l=r+1)
        {
            r=min(n/(n/l),m/(m/l));
            res+=(sum[r]-sum[l-1])*g[n/l]*g[m/l];
        }
        write(res);pc('\n');
    }
    return 0;
}


其他例题

洛谷P2257 YY的GCD 题解

洛谷P2260 [清华集训2012]模积和 题解

洛谷P2522 [HAOI2011]Problem b 题解


参考文献

[1] https://oi-wiki.org/math/number-theory/sqrt-decomposition/

强烈推荐作图工具:wolfram|Alpha (雾


文章作者: q779
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 q779 !
评论
  目录