前缀和与差分 Krains 2020-07-28 16:05:15
# 一维前缀和
前缀和的定义如下:
s[0]=0s[0] = 0s[0]=0
s[i]=a[0]+a[1]+a[2]+...+a[i−1]s[i] = a[0]+a[1]+a[2]+...+a[i-1]s[i]=a[0]+a[1]+a[2]+...+a[i−1]
有了前缀和数组,我们可以用O(1)O(1)O(1)的时间计算区间[r, l]
的和:
s[r+1]−s[l]s[r+1]-s[l] s[r+1]−s[l]
public int preSum(int[] a, int l, int r){
int n = a.length;
int[] s = new int[n+1];
// 计算前缀和数组
for(int i = 1; i <= n; i++){
s[i] = s[i-1] + a[i-1];
}
return s[r+1] - s[l];
}
# 二维前缀和
计算公式
s[i][j]=s[i−1][j]+s[i][j−1]−s[i−1][j−1]+a[i−1][j−1]s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i-1][j-1] s[i][j]=s[i−1][j]+s[i][j−1]−s[i−1][j−1]+a[i−1][j−1]
区间[x1, y1]
到[x2,y2]
和:
s[x2+1][y2+1]−s[x2+1][y1]−s[x1][y2+1]+s[x1][y1]s[x2+1][y2+1]-s[x2+1][y1]-s[x1][y2+1]+s[x1][y1] s[x2+1][y2+1]−s[x2+1][y1]−s[x1][y2+1]+s[x1][y1]
public int preSum(int[][] a, int x1, int y1, int x2, int y2){
int m = a.length;
int n = a[0].length;
int[][] s = new int[m+1][n+1];
for(int i = 1; i <= m; i++){
for(int j = 1; j <= n; j++){
s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i-1][j-1];
}
}
return s[x2+1][y2+1]-s[x2+1][y1]-s[x1][y2+1]+s[x1][y1];
}
# 一维差分
对于a数组来说,定义一个数组b,使得b的前缀和等于a,b就是a的差分数组。
想要对a数组区间[l, r]
加c,只需要
b[l]+=c,b[r]+=cb[l]+=c,b[r]+=c b[l]+=c,b[r]+=c
tip:此处数组中1开始
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int m = scanner.nextInt();
int[] a = new int[n+1];
int[] b = new int[n+2]; // 得多开一个空间
for(int i = 1; i <= n; i++){
a[i] = scanner.nextInt();
}
// 构造b数组
for(int i = 1; i <= n; i++){
new Main().insert(b, i, i, a[i]);
}
while((m--) != 0){
int l = scanner.nextInt();
int r = scanner.nextInt();
int c = scanner.nextInt();
new Main().insert(b, l, r, c);
}
for(int i = 1; i <= n; i++){
a[i] = b[i] + a[i-1];
System.out.print(a[i]+" ");
}
}
// 给a数组区间[l, r]加上c
public void insert(int[] b, int l, int r, int c){
b[l] += c;
b[r+1] -= c;
}
}
# 二维差分
对[x1, y1]
到[x2, y2]
这个矩形所围的区域加上C,它的差分矩阵就该进行如下操作。
b[x1][y1]+=cb[x1][y1]+=c b[x1][y1]+=c
b[x2+1][y1]−=cb[x2+1][y1]-=c b[x2+1][y1]−=c
b[x1][y2+1]−=cb[x1][y2+1]-=c b[x1][y2+1]−=c
b[x2+1][y2+1]+=cb[x2+1][y2+1]+=c b[x2+1][y2+1]+=c
最后通过b的前缀和就能够还原a。
tip: 此处数组从1开始
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int m = scanner.nextInt();
int q = scanner.nextInt();
int[][] a = new int[n+1][m+1];
int[][] b = new int[n+2][m+2];
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
a[i][j] = scanner.nextInt();
// 构造差分矩阵
new Main().insert(b, i, j, i, j, a[i][j]);
}
}
while((q--) != 0){
int x1 = scanner.nextInt();
int y1 = scanner.nextInt();
int x2 = scanner.nextInt();
int y2 = scanner.nextInt();
int c = scanner.nextInt();
new Main().insert(b, x1, y1, x2, y2, c);
}
// 计算b矩阵的二维前缀和,即a
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
a[i][j] = a[i-1][j] + a[i][j-1] - a[i-1][j-1] + b[i][j];
System.out.print(a[i][j] + " ");
}
System.out.println();
}
}
public void insert(int[][] b, int x1, int y1, int x2, int y2, int c){
b[x1][y1] += c;
b[x2+1][y1] -= c;
b[x1][y2+1] -= c;
b[x2+1][y2+1] += c;
}
}
# 题目练习
# 求满足条件的子数组数量
# 560. 和为K的子数组
我们知道两个前缀和相减能够得到一个连续数组的和,比如s[i+1]-s[j]
就表示[j, i]
的区间和。
求出数组的前缀和,用i遍历前缀和数组,如果有i之前的元素j能够使得s[i]-s[j]=k
,则说明[j, i-1]
是一个满足条件的子数组,我们只需要统计之前等于s[j]=s[i]-k
的元素个数就是当前满足条件的子数组个数。
class Solution {
public int subarraySum(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<>();
int[] s = new int[nums.length + 1];
for(int i = 1; i < s.length; i++){
s[i] = s[i-1] + nums[i-1];
}
int count = 0;
for(int i = 0; i < s.length; i++){
int j = s[i] - k;
if(map.containsKey(j)){
count += map.get(j);
}
map.put(s[i], map.getOrDefault(s[i], 0) + 1);
}
return count;
}
}
# 974. 和可被 K 整除的子数组
我们要判断的是(s[j] - s[i]) % K
是否等于0。那么根据同余定理,(s[j] - s[i]) % K = s[j] % K - s[i] % K
,所以,若要(s[j] - s[i] )% K = 0
只要 s[j] % K = s[i] % K
,我们对前缀和都mod上K,如果s[i]==s[j],i<j
,则说明区间[j-1, i]
和能够被K整除,我们统计这样的元素对数即为答案。
class Solution {
public int subarraysDivByK(int[] A, int K) {
int n = A.length;
int[] s = new int[n+1];
Map<Integer, Integer> map = new HashMap<>();
for(int i = 1; i <= n; i++){
s[i] = s[i-1] + A[i-1];
// 让负数的余数转为正数
s[i] = (s[i]%K + K) % K;
}
int count = 0;
for(int i = 0; i <= n; i++){
if(map.containsKey(s[i])){
count += map.get(s[i]);
}
map.put(s[i], map.getOrDefault(s[i], 0) + 1);
}
return count;
}
}
- MATLAB技巧——imshow多张图片
- MATLAB技巧——sort和sortrows函数
- Python对商品属性进行二次分类并输出多层嵌套字典
- 通过shell得到数据库中权限的脚本(r2笔记77天)
- 用Python实现PCA和MDA降维和聚类
- 通过shell解析dump生成parfile(r2笔记76天)
- Web Spider实战1——简单的爬虫实战(爬取"豆瓣读书评分9分以上榜单")
- 如何用R语言从网上读取多样格式数据
- C/C++——生成随机数
- PHP基础——PHP数组
- 使用shell抽取html数据之二(r2笔记75天)
- Python爬取链家网数据:新房楼盘价格分析
- 【编程基础】Java里面如何对字符串排序?
- 计算广告——广告定向实践
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- python之循环控制语句
- python之结合if条件判断和生成随机数的相关知识,完成石头剪刀布的游戏
- AtCoder Beginner Contest 172
- python的import与 from……import
- 题目 1159: [偶数求和]
- 回溯法求组合问题
- P1567 统计天数
- P1089 津津的储蓄计划(模拟训练)
- 二分练习 --D - Trailing Zeroes (III)
- HDU 5806
- B - They Are Everywhere CodeForces - 701C
- 尺取练习 -A - A - Stages (水题压压惊)
- 填坑-回溯-预习 之 二分-尺取大总结
- 深入浅出理解动态规划(一) | 交叠子问题
- 深入浅出理解动态规划(二) | 最优子结构