数论分块

数论分块

数论分块可以快速计算一些含有除法向下取整的和式 (即形如 i=1nf(i)g(ni) 的和式)。当可以在 O(1) 内计算 f(r)f(l) 或已经预处理出 f 的前缀和时,数论分块就可以在 O(n) 的时间内计算上述和式的值。
它主要利用了富比尼定理 (Fubinistheorem),将 ni 相同的数打包同时计算。

引入

洛谷 P1403 约数研究

题目转换一下就是求 f(n)=i=1nni

朴素做法,遍历 1n 求和,时间复杂度 O(n)

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int ans = 0;
        for (int i = 1; i <= n; ++i) {
            ans += n / i;
        }
        System.out.println(ans);
    }
}

不能解决 109 以上的数据范围,考虑优化

n=21 时,ni 的值如下:

i 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
ni 21 10 7 5 4 3 3 2 2 2 1 1 1 1 1 1 1 1 1 1 1

观察发现,ni 的取值在连续的一段区间内是相同的,是”一块一块“的

如果我们知道了每一块的值和长度(左右边界),也就可以使用乘法运算来代替加法运算了

求某一值所在块的右端点

假设求 i 所在块的右端点

i 所在块的值为 k=ni,则 kni,所以 nknni=i=i

因此,imax=nk=nni,即右端点为 nni

实现

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        long n = sc.nextLong();
        long ans = 0;
        for (long l = 1, r; l <= n; l = r + 1) {
            //取min防止越界
            r = Math.min(n / (n / l),n);
            ans += n / l * (r - l + 1);
        }
        System.out.println(ans);
    }
}

时间复杂度分析

 分块的块数 2n

证明:

in 时, nin 种取值。
i>n 时, nin,ni 至多有 n 种取值。

综上,分块的块数2n

因此,时间复杂度为 O(2n)=O(n)

例题

约数和

题意概述

原题链接:P2424 约数和 - 洛谷

给定正整数 xyx<y,求 i=xyd|id

解题思路

[x,y] 的函数值可以先求得 [1,x1][1,y] 再相减,即i=xyd|id=i=1yd|idi=1x1d|id

因此,该问题变成了求出 i=1nd|id

洛谷 P1403 约数研究类似,求每个数的约数和,可以转换成求 1n 中有哪些数含 i,其中 i[1,n],换言之,转化为枚举因子 d

1n 的约数中有 ndd ,则 d 对答案的贡献为 nd×d

因此,式子可以转化为 d=1nnd×d

因为乘法满足分配律,对于 nd 值相等的一块区间,满足 d=lrnd×d=nd×d=lrd=nd×(l+r)×(rl+1)2

Code

import java.util.Scanner;

public class Main {
    //计算 1~n 所有约数的和
    static long calculate(long n) {
        long ans = 0;
        for (long l = 1, r; l <= n; l = r + 1) {
            r = Math.min(n / (n / l), n);
            ans += (l + r) * (r - l + 1) / 2 * (n / l);
        }
        return ans;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int x = sc.nextInt(), y = sc.nextInt();
        System.out.println(calculate(y) - calculate(x - 1));
    }
}

余数求和

题意概述

原题链接:P2261 余数求和 - 洛谷

给定正整数 nk,求 f(n,k)=i=1nk mod i

解题思路

(1)f(n,k)=i=1nk mod i=i=1n(kki×i)=i=1nki=1nki×i=n×ki=1nki×i

和式部分与上一题仅为分子变了

Code

注意点:

  1. 数据范围,两数相乘可能超出 int 范围
  2. k<l 时,不能再求右端点,会除零错误
import java.util.Scanner;

public class Main {
    //计算 1~n 所有约数的和
    static long calculate(long n, long k) {
        long ans = 0;
        //当左端点小于等于k时,才有右端点
        for (long l = 1, r; l <= n && l <= k; l = r + 1) {
            r = Math.min(k / (k / l), n);
            ans += (l + r) * (r - l + 1) / 2 * (k / l);
        }
        return ans;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt(), k = sc.nextInt();
        //两数相乘可能超出int
        System.out.println((long) n * k - calculate(n, k));
    }
}

参考资料

【数论】整除分块(数论分块)_辞树c的博客-CSDN博客

549 整除分块(数论分块)- 董晓算法 - 哔哩哔哩

数论分块 - OI Wiki

posted @   Cattle_Horse  阅读(51)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
点击右上角即可分享
微信分享提示