This commit is contained in:
HuangHai
2025-08-30 18:35:01 +08:00
commit 1f397eca87
6258 changed files with 1487241 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int s[N];
// 一维前缀和
int main() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
s[i] = s[i - 1] + x;
}
while (m--) {
int l, r;
cin >> l >> r;
printf("%d\n", s[r] - s[l - 1]);
}
return 0;
}

View File

@@ -0,0 +1,46 @@
##[$AcWing$ $795$ 前缀和](https://www.acwing.com/problem/content/797/)
### 一、题目描述
输入一个长度为 $n$ 的整数序列。
接下来再输入 $m$ 个询问,每个询问输入一对 $l,r$。
对于每个询问,输出原序列中从第 $l$ 个数到第 $r$ 个数的和。
**输入格式**
第一行包含两个整数 $n$ 和 $m$。
第二行包含 $n$ 个整数,表示整数数列。
接下来 $m$ 行,每行包含两个整数 $l$ 和 $r$,表示一个询问的区间范围。
**输出格式**
共 $m$ 行,每行输出一个询问的结果。
**数据范围**
$1≤l≤r≤n,1≤n,m≤100000,−1000≤数列中元素的值≤1000$
### 二、实现代码
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int q[N];
int s[N];
// 一维前缀和
int main() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> q[i];
s[i] = s[i - 1] + q[i];
}
while (m--) {
int l, r;
cin >> l >> r;
printf("%d\n", s[r] - s[l - 1]);
}
return 0;
}
```

View File

@@ -0,0 +1,23 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int a[N][N];
int s[N][N];
int main() {
int n, m, q;
cin >> n >> m >> q;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) {
cin >> a[i][j];
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];
}
while (q--) {
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
printf("%d\n", s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]);
}
return 0;
}

View File

@@ -0,0 +1,29 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int n, m;
int a[N], b[N];
/**
* 功能:差分计算
* @param l 左边界
* @param r 右边界
* @param c 值
*/
void insert(int l, int r, int c) {
b[l] += c;
b[r + 1] -= c;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> a[i], insert(i, i, a[i]);
while (m--) {
int l, r, c;
cin >> l >> r >> c;
insert(l, r, c);
}
for (int i = 1; i <= n; i++)
a[i] = a[i - 1] + b[i], printf("%d ", a[i]);
return 0;
}

View File

@@ -0,0 +1,46 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int a[N][N], b[N][N];
int n, m, q;
/**
* 功能:二维差分构建
* @param x1 左上角横坐标
* @param y1 左上角纵坐标
* @param x2 右下角横坐标
* @param y2 右下角纵坐标
* @param c 值
*/
void insert(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;
}
int main() {
cin >> n >> m >> q;
//读入并构建
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
cin >> a[i][j], insert(i, j, i, j, a[i][j]);
//q次区域变化
while (q--) {
int x1, y1, x2, y2, c;
cin >> x1 >> y1 >> x2 >> y2 >> c;
insert(x1, y1, x2, y2, c);
}
//还原二维数组
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];
printf("%d ", a[i][j]);
}
printf("\n");
}
return 0;
}

View File

@@ -0,0 +1,25 @@
#include<bits/stdc++.h>
using namespace std;
const int N=3e3+5, INF=0x3f3f3f3f;
int a[N],s[N];
int main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
s[i]=s[i-1]+a[i];
}
int ans=INF;
//ö<>ٵ<EFBFBD><D9B5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>յ<EFBFBD>
//for(int i=m;i<=n;i++) ans=min(ans,s[i]-s[i-m]);
//ö<>ٵ<EFBFBD><D9B5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
for(int i=1;i+m-1<=n;i++){
int L=i,R=i+m-1;
ans=min(ans,s[R]-s[L-1]);
}
cout<<ans<<endl;
return 0;
}

View File

@@ -0,0 +1,30 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int a[N][N], b[N][N];
int n, m, q;
void insert(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;
}
int main() {
cin >> n >> m >> q;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) cin >> a[i][j], insert(i, j, i, j, a[i][j]);
while (q--) {
int x1, y1, x2, y2,c;
cin >> x1 >> y1 >> x2 >> y2>>c;
insert(x1, y1, x2, y2, c);
}
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];
printf("%d ", a[i][j]);
}
printf("\n");
}
return 0;
}

View File

@@ -0,0 +1,176 @@
### 一、一维前缀和
场景模拟:
老师让 **班长糖豆** 帮着计算一下全班同学语文考试的总分,老师负责读每个同学的分数,糖豆负责计算。
老师:“第一名,张三 $100$分”, 糖豆记录如下:$100$分
老师:“第二名,李四 $99$分”, 糖豆 **擦去** $100$,修改为:$199$分
老师:“第三名,王五 $98$分”, 糖豆 **擦去** $199$,修改为:$297$分
...
老师:“第四十五名,赵九 $60$分”, 糖豆 **擦去** $4179$,修改为:$4239$分
完事了,糖豆汇报总分:“$4239$分!任务结束!”
老师想了一想,问了一句:“那前十名共多少分?”
糖豆有点懵,因为把前十位的结果已经擦去啦!只能让老师从第一名开始到第十名再读一次。^_^
老师又想问:"那前二十名共多少分?"
糖豆彻底懵了,只能让老师从第一名开始到第二十名再读一次。^_^
老师也疯了!!!
看来这个办法不太行,老师的需求总变化!
那有什么办法呢??糖豆会有办法的:
<font color='red'>记录前$i$个同学的分数总和!不擦!</font>
来吧,老师,你说你想要啥?
我想要前$20$名的分数总和!没问题,我记的就是这个,给你!
我想要$20$至$30$名的分数总和!啊???还想这么要?怎么办呢?
我们用数学的公式来描述一下,这样方便说明:
$a[i]$代表$i$号同学分数,$s[i]$代表$i$号同学及他以前的所有同学的分数总和。
那么有下面的关系式:
$s[i-1]=a[1]+a[2]+a[3]+...+a[i-1]$ ①
$s[i]=\ \ \ \ \ \ \ a[1]+a[2]+a[3]+...+a[i-1]+a[i]$ ②
将①式代入②式,得到
$s[i]=s[i-1]+a[i]$ ③
如果记录了这个$s[i]$,就能回答好多的问题:
1、第$5$名同学的分数是多少?
答:$s[5]-s[4]$ ,为啥呢?因为③式的变形 $a[i]=s[i]-s[i-1]$
2、第$1$名到第$10$名同学的分数和是多少?
答:$s[10]$ 为啥呢?因为前缀和定义就是从`1`到`i`的数据和嘛。
3、第$5$名到第$10$名同学的分数和是多少?
答:$s[10]-s[4]$
为啥呢?
其实我们想求的是:$a[5]+a[6]+a[7]+a[8]+a[9]+a[10]$
我们使用$s[i]$来构建上面的式子:
$s[4]=a[1]+a[2]+a[3]+a[4]$ ①
$s[10]=a[1]+a[2]+a[3]+a[4]+a[5]+...+a[10]$ ②
将①式代入②式,就是$s[10]=s[4]+a[5]+a[6]+a[7]+a[8]+a[9]+a[10]$
移项得到:$s[10]-s[4]=a[5]+a[6]+a[7]+a[8]+a[9]+a[10]$
<font color='red'>注意:
前缀和一般从数下标$1$开始,这是因为它的定义是$s[i]=s[i-1]+a[i]$,如果$i$从$0$开始,$s$数组下标就会出现负数,这样还需一堆$if$判断,麻烦,所以,一般为了代码简单,我们都把$a[0]=0$,通常在全局变量区域里定义$a$数组,这样连$a[0]=0$也省略了,$s[0]$其实也是定义在全局变量区域的,所以$s[0]=0$,这样操作代码就简单了。
</font>
#### 1. 使用场景
给定一个原始数组,后面需要多次查询某一个的数值和,比如 $1$ $2$ $3$ $4$ $6$ $3$ $7$ $8$ $9$ $10$,$10$个数字,需要问$N$次,每次问从`x`到`y`的位置,相加的和是几。
如果按普通想法,就是每问一次就计算一次,不利用以前的结果。这样假设每次的`l`到`r`的距离是`m`,很显然,共需要`m*N`次操作,如果使用了前缀和的预处理,计算一次前缀和,就是`N`次运算,得到一个结果数组`s[N]`,以后每次查询都是 $s[r]-s[l-1]$,就是一次运算,快了很多。
####[$AcWing$ $795$. 前缀和](https://www.acwing.com/problem/content/797/)
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int q[N];
int s[N];
//一维前缀和
int main() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> q[i];
s[i] = s[i - 1] + q[i];
}
while (m--) {
int l, r;
cin >> l >> r;
printf("%d\n", s[r] - s[l - 1]);
}
return 0;
}
```
###[$AcWing$ $796$. 子矩阵的和](https://www.acwing.com/problem/content/798/)
**什么是二维前缀和?**
`a[N][M]`假设为原数组,`s[N][M]`为二维前缀和数组,`s[i][j]`的意义是:原数组前`i`行,前`j`列的数组元素值的和。
说白了,就是`1->i`行,`1->j`列的所有元素值的和,就是左上角的格子内容和:
![1.png](https://cdn.acwing.com/media/article/image/2021/04/07/64630_55a5e0a197-1.png)
本质上和一维前缀和是一个意思,一维是第`1`个元素到第`n`个元素的累加和,二维是从左上角到`(i,j)`的累加和。
#### 1、公式的推导
$s[3][3]=a[1][1]+a[1][2]+a[1][3]$
$\ \ \ \ \ \ \ \ \ \ +a[2][1]+a[2][2]+a[2][3]$
$\ \ \ \ \ \ \ \ \ \ +a[3][1]+a[3][2]+a[3][3]$
$s[3][2]=a[1][1]+a[1][2]$
$\ \ \ \ \ \ \ \ \ \ +a[2][1]+a[2][2]$
$\ \ \ \ \ \ \ \ \ \ +a[3][1]+a[3][2]$
$s[2][3]=a[1][1]+a[1][2]+a[1][3]$
$\ \ \ \ \ \ \ \ \ \ +a[2][1]+a[2][2]+a[2][3]$
$s[2][2]=a[1][1]+a[1][2]$
$\ \ \ \ \ \ \ \ \ \ +a[2][1]+a[2][2]$
观察上面4个式子,我们想要计算$s[3][3]$,还不想用笨办法,一个个用$a[i][j]$来累加出来,就可以利用已经算出来的结果值进行运算获得!
$s[3][3]=s[3][2]+s[2][3]-s[2][2]+a[3][3]$
抽象一下,就是:(如果感觉看代数式子不好理解,就用上面图理解就行啦!)
$s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j]$
#### 2、计算子区域的前缀和
![](https://img2020.cnblogs.com/blog/8562/202109/8562-20210901144235551-492905380.png)
#### 3、C++ 代码
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int a[N][N];
int s[N][N];
int main() {
int n, m, q;
cin >> n >> m >> q;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) {
cin >> a[i][j];
s[i][j] = s[i - 1][j] + s[i][j - 1] + a[i][j] - s[i - 1][j - 1];
}
while (q--) {
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
printf("%d\n", s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]);
}
return 0;
}
```
#### 练习题
**[(一维前缀和) 洛谷 $P1614$ 爱与愁的心痛](https://blog.csdn.net/m0_50683128/article/details/110673195)**
**[前缀和和差分洛谷题单总结](https://blog.csdn.net/piqihaoshaonian/article/details/127515000)**

View File

@@ -0,0 +1,192 @@
### [$AcWing$ $797$. 差分](https://www.acwing.com/problem/content/799/)
定义:$b[i]=a[i]-a[i-1]$,称$b$数组是$a$数组的差分数组。
举个栗子:
$a=[0,1,2,3,4,5]$
$b=[0,1,1,1,1,1]$
为啥呢?
$a[5]-a[4]=b[5]$
$a[4]-a[3]=b[4]$
$a[3]-a[2]=b[3]$
$a[2]-a[1]=b[2]$
$a[1]-a[0]=b[1]$
算一下,就是$b=[1,1,1,1,1]$
把上面五个式子左边加在一起,右边也加在一起,两边还应该相等。就是:
$a[5]-a[4]+a[4]-a[3]+a[3]-a[2]+a[2]-a[1]+a[1]-a[0]=b[5]+b[4]+b[3]+b[2]+b[1]$
$a[5]=b[1]+b[2]+b[3]+b[4]+b[5]$
太奇妙了!<font color='red'>**$a$数组是$b$数组的一维前缀和数组!**</font>
**小结:**
(1)、$a$数组中,每个数字**后面减前面得到的数字填入$b$数组**,$b$数组就叫$a$数组的差分数组。
(2)、同时,$a$数组就是$b$数组的前缀和数组。
(3)、**通过“叠加”差分数组,就可以还原出“原数组”的每一个数字!**
(4)、**前缀和与差分互为逆运算,有原数组可以计算出差分数组;有差分数组,也可以还原成原数组。**
#### 2、一维差分作用
使用场景:
**在某个区间$[l,r]$的多次操作都加上(或减去)一个数$x$时,一维差分可以大大提高运算速度。**
举个栗子:
<center><img src='https://img2020.cnblogs.com/blog/8562/202109/8562-20210907140423790-882246210.png'></center>
总结公式:
```c++
void insert(int l,int r,int c){
b[l]+=c;
b[r+1]-=c;
}
```
我们利用刚才的结论:**通过“叠加”差分数组,就可以还原出“原数组”的每一个数字!**!这玩意就整一回合适吗?不合适,还不够费功夫的呢!生成一维差分也就算了,从一维差分数组还原回原数组就需要$n$次运算,整不好还赔了呢!但如果是多次区间加减运算,就合适了!
#### 3、一维差分应用
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int n, m;
int a[N], b[N];
/**
* 功能:差分计算
* @param l 左边界
* @param r 右边界
* @param c 值
*/
void insert(int l, int r, int c) {
b[l] += c;
b[r + 1] -= c;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
insert(i, i, a[i]);
//或者 b[i]=a[i]-a[i-1];
}
while (m--) {
int l, r, c;
cin >> l >> r >> c;
insert(l, r, c);
}
for (int i = 1; i <= n; i++)
a[i] = a[i - 1] + b[i], printf("%d ", a[i]);
return 0;
}
```
### [$AcWing$ $798$. 差分矩阵](https://www.acwing.com/problem/content/800/)
#### 1、二维差分定义
我们有一个矩阵,如下图所示。
![](https://img2020.cnblogs.com/blog/8562/202109/8562-20210907141102465-1061848020.png)
根据二维前缀和表示的是右上角矩形的和,由于差分只涉及前面相邻的数(由一维可以推出),并且由前面范围的数相加得到这个位置的数。那么类比二维前缀和和一维差分,可以简单推测出
**二维差分的公式**
$b[i][j]=a[i][j]-a[i-1][j]-a[i][j-1]+a[i-1][j-1]$
**如何从差分矩阵得到原矩阵呢?**[就是二维前缀和公式]
$a[i][j] = a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1] + b[i][j]$
**举个栗子**
比如,我们有一个矩阵 a,如下所示:
```c++
1 2 4 3
5 1 2 4
6 3 5 9
```
那么对应的二维差分矩阵 b 如下:
```c++
1 1 2 -1
4 -5 -1 3
1 1 1 2
```
#### 2、二维差分用途
和一维差分的用途基本一致,在一个二维矩阵中,有多块区间需要增加或减少一个数值,多次操作后求最终的矩阵内容。如果按照传统办法,就是二层循环,复杂度很高,如果预处理出一个二维的差分矩阵,以后的多轮操作都转为了4次加加减减操作,可以视为$O(1)$级别的时间复杂度,运算效率将得到极大提高。
#### 3、二维差分构建
如果我们要在左上角是 `(x1,y1)`,右下角是 `(x2,y2)` 的矩形区间每个值都 `+c`,如下图所示
![1.png](https://cdn.acwing.com/media/article/image/2021/04/07/64630_88b316d097-1.png)
在我们要的区间开始位置`(x1,y1)`处 `+c`,根据前缀和的性质,那么它影响的就是整个黄色部分,多影响了两个蓝色部分,所以在两个蓝色部分 `-c` 消除 `+c` 的影响,而两个蓝色部分重叠的绿色部分多了个 `-c` 的影响,所以绿色部分 `+c` 消除影响。所以对应的计算方法如下:
```
void insert(int x1, int y1, int x2, int y2, int c){
b[x1][y1] += c; //开始位置增加C
b[x2 + 1][y1] -= c; //见上面的图,把第二行的蓝色区域减去C
b[x1][y2 + 1] -= c; //见上面的图,把第一行的蓝色区域减去C
b[x2 + 1][y2 + 1] += c; //交叉位置被减了两次,需要补回来。
}
```
#### 4、二维差分应用
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int a[N][N], b[N][N];
int n, m, q;
/**
* 功能:二维差分构建
* @param x1 左上角横坐标
* @param y1 左上角纵坐标
* @param x2 右下角横坐标
* @param y2 右下角纵坐标
* @param c 值
*/
void insert(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;
}
int main() {
cin >> n >> m >> q;
//读入并构建
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
cin >> a[i][j], insert(i, j, i, j, a[i][j]);
//q次区域变化
while (q--) {
int x1, y1, x2, y2, c;
cin >> x1 >> y1 >> x2 >> y2 >> c;
insert(x1, y1, x2, y2, c);
}
//还原二维数组
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];
printf("%d ", a[i][j]);
}
printf("\n");
}
return 0;
}
```