前缀和与差分 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;
}
}
- 多线程之策略模式
- 文件上传的动作不能太俗,必须页面无刷新上传
- 这次真的忽略了一些ActiveMQ内心的娇艳
- 多线程编程:阻塞、并发队列的使用总结
- 多线程编程:多线程并发制单的开发记录【一】
- 如何使用线程锁来提高多线程并发效率
- 如何在分布式环境中同步solr索引库和缓存信息
- 如何在分布式环境中同步solr索引库和缓存信息
- Info模式下的隐形杀手(SpringMVC同时使用<mvc:resources.../>和FormattingConversionServiceFactoryBean时出现的问题)
- 关于web.xml3.0启动报错
- [机器学习]-[数据预处理]-中心化 缩放 KNN(一)
- 信息安全不可或缺应用交付 还需安全交付
- 一张图带你看懂区块链项目生态
- python环境的安装
- 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 数组属性和方法
- Linux 命令行通配符及转义符的实现
- Python爬虫抓取指定网页图片代码实例
- PHP变量作用域(全局变量&局部变量)&global&static关键字用法实例分析
- CentOS 7 安装Chrome浏览器的方法
- PHP高级编程之消息队列原理与实现方法详解
- thinkphp5.1框架模板布局与模板继承用法分析
- Linux内核设备驱动之内存管理笔记整理
- Matplotlib 绘制饼图解决文字重叠的方法
- Yii 实现数据加密和解密的示例代码
- 3分钟看懂Python后端必须知道的Django的信号机制
- Hadoop 2.X新特性回收站功能的讲解
- php开发论坛系统
- 详解python中GPU版本的opencv常用方法介绍
- 详解Python IO编程
- PHP 使用位运算实现四则运算的代码