前缀和:一维与二维

本质上有动态规划的思想。

文章目录

一、一维前缀和

牛客 DP34.【模板】前缀和

设 dp[i] 表示数组前 i 个元素之和,并额外定义 dp[0] = 0:

text 复制代码
dp[i] = dp[i - 1] + arr[i]

查询闭区间 [l, r] 时,dp[r] 包含第 1 个到第 r 个元素,再减去第 1 个到第 l - 1 个元素:

text 复制代码
sum(l, r) = dp[r] - dp[l - 1]

代码中的数组名虽然是 dp,但它保存的是预处理得到的前缀和,不是动态规划中的最优子结构结果。

java 复制代码
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        int q = in.nextInt();
        int[] arr = new int[n + 1];
        long[] dp = new long[n + 1];
        for (int i = 1; i < n + 1; i++) {
            arr[i] = in.nextInt();
            dp[i] = dp[i - 1] + arr[i];
        }

        while (in.hasNextInt()) {
            int a = in.nextInt();
            int b = in.nextInt();
            System.out.println(dp[b] - dp[a - 1]);
        }
    }
}

题目中的元素绝对值可达 10^9,区间和可能超过 int 范围,因此前缀和数组使用 long[]。

二、二维前缀和

牛客 DP35.【模板】二维前缀和

设 dp[i][j] 表示左上角 (1, 1) 到右下角 (i, j) 这一整块矩形的元素和。构造时先相加上方矩形与左侧矩形,但它们重复计算了左上角重叠区域,因此需要减去一次,再加上当前元素:

text 复制代码
dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
         - dp[i - 1][j - 1] + arr[i][j]

查询左上角 (x1, y1) 到右下角 (x2, y2) 的子矩形时,同样使用容斥:

text 复制代码
sum = dp[x2][y2] - dp[x1 - 1][y2]
    - dp[x2][y1 - 1] + dp[x1 - 1][y1 - 1]

前两次减法去掉目标矩形上方与左侧的区域,但左上角区域被减了两次,所以最后要补回一次。

java 复制代码
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        int m = in.nextInt();
        int q = in.nextInt();

        int[][] arr = new int[n + 1][m + 1];
        long[][] dp = new long[n + 1][m + 1];

        for (int i = 1; i < n + 1; i++) {
            for (int j = 1; j < m + 1; j++) {
                arr[i][j] = in.nextInt();
                dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
                        - dp[i - 1][j - 1] + arr[i][j];
            }
        }

        while (in.hasNextInt()) {
            int x1 = in.nextInt();
            int y1 = in.nextInt();
            int x2 = in.nextInt();
            int y2 = in.nextInt();
            System.out.println(dp[x2][y2] - dp[x1 - 1][y2]
                    - dp[x2][y1 - 1] + dp[x1 - 1][y1 - 1]);
        }
    }
}

三、零边界

为什么要把原数组向后平移一位?如果一维前缀和直接把 dp[0] 作为第一个值不是更符合直觉吗?

但是这样我们在实操的时候会遇到许多不便之处,往往需要繁杂的分类讨论,因此我们补出值为 0 的边界,用来统一公式。

例如查询从第 1 个元素开始第 1 个元素结束的区间时,公式会访问 dp[1 - 1],也就是 dp[0]。二维前缀和同理:当查询区域贴着上边界或左边界时,公式会访问第 0 行或第 0 列。预留零边界后,不需要为这些情况额外分类,也不会出现负数下标,从而我们花费了少量的空间换来了简洁的代码,优雅的思路和性能的提升。

相关推荐
灯澜忆梦20 分钟前
【面向对象编程C++】| 基础语法
java·c++·算法
禾小西25 分钟前
Redis 数据结构:快速的 Redis 有哪些慢操作?
数据结构·数据库·redis
小蒋观天下28 分钟前
专项方案:大场景港口AI安防、多干扰环境下的算法调优与落地实操
人工智能·深度学习·算法·安全·机器学习·计算机视觉·ai大模型
Evand J36 分钟前
【电机滤波例程5】渐消因子自适应扩展卡尔曼滤波(EKF),MATLAB。PMSM负载突变下的状态估计。附下载链接
算法·matlab
H.莓飛1 小时前
【数据结构】二叉树_OJ题
linux·开发语言·数据结构·算法
好想像大佬一样能够ak所有2 小时前
Leetcode35.搜索插入位置
数据结构·算法·leetcode
aqiu1111112 小时前
【C++算法打怪专栏】LeetCode 165. 比较版本号(双指针与字符串流法)
c++·算法·leetcode
黄金龙PLUS2 小时前
如何基于置换算法来设计对称加密算法
算法·网络安全·密码学·哈希算法·同态加密
不会就选b2 小时前
算法日常・每日刷题--<动态规划>14
算法
赛博守夜人2 小时前
跨境业务与出海合规之九:海外买量投放与营销虚假流量(Ad Fraud):跨国机房群控与归因欺诈识别算法
算法·安全