2

算法基础(五)| 差分算法及模板详解

 1 year ago
source link: https://blog.51cto.com/u_15736437/5745962
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

算法基础(五)| 差分算法及模板详解

精选 原创

timerring 2022-10-11 11:18:24 博主文章分类:算法基础教程 ©著作权

文章标签 差分算法 c++ 文章分类 C/C++ 编程语言 yyds干货盘点 阅读数178

⭐写在前面的话:本系列文章旨在复习算法刷题中常用的基础算法与数据结构,配以详细的图例解释,总结相应的代码模板,同时结合例题以达到最佳的学习效果。本专栏面向算法零基础但有一定的C++基础的学习者。若C++基础不牢固,可参考:​ ​10min快速回顾C++语法​​,进行语法复习。

🔥本文已收录于算法基础系列专栏: ​ ​算法基础教程​​ 免费订阅,持续更新。

算法基础(五)| 差分算法及模板详解_c++

差分思想和前缀和是相反的。

首先我们先定义数组a, 其中a[1],a[2]...a[n]作为前缀和。

然后构造数组b,b[1],b[2]...b[n]为差分数组。其中通过差分数组的前缀和来表示a数组,即a[n] = b[1] + b[2]+...+b[n]。

一维差分数组的构造也很简单,即a[1] = b[1], b[2] = a[2] - a[1], b[n] = a[n] - a[n-1];

注意:刚开始时可以初始化数组a,b全部为0,输入a数组后;在构造时,只需要将b[1]看做在[1, 1]区间上加上a[1]; b[2] 看作在[2, 2]区间上加上a[2];

//eg:对于b[1]:
b[1] = 0 + a[1];
b[2] = 0 - a[1]; //最终:b[1] = a[1]
//对于b[2]:
b[2] = b[2] + a[2]; //==> 最终:b[2] = a[2] - a[1]
b[3] = b[3] - b[2];

差分数组的好处是可以简化运算,例如想要给一个区间 [l,r] 上的数组加一个常数c,原始的方法是依次加上c,这样的时间复杂度是O(n)的。但是如果采用差分数组的话,可以大大降低时间复杂度到O(1)。

由于a[n] = b[1] + b[2]+...+b[n],因此只需要将b[l] = b[l] + c 即可,这样l之后的数字会依次加上常数c,而在 b[r]处,将b[r+1] = b[r+1] - c ,这样r之后的数组又会恢复原值,仅需要处理这两个边界的差分数组即可,时间复杂度大大降低。

算法基础(五)| 差分算法及模板详解_c++_02

例题:差分

输入一个长度为 n 的整数序列。

接下来输入 m 个操作,每个操作包含三个整数 l,r,c,表示将序列中 [l,r] 之间的每个数加上 c。

请你输出进行完所有操作后的序列。

输入格式

第一行包含两个整数 n 和 m。

第二行包含 n 个整数,表示整数序列。

接下来 m 行,每行包含三个整数 l,r,c表示一个操作。

输出格式

共一行,包含 n 个整数,表示最终序列。

数据范围

1≤n,m≤100000,1≤l≤r≤n, −1000≤c≤1000, −1000≤整数序列中元素的值≤1000

输入样例:

6 3
1 2 2 1 2 1
1 3 1
3 5 1
1 6 1

输出样例:

3 4 5 3 4 2
#include<iostream>
using namespace std;

const int N = 100010;

int m,n;
int a[N],b[N];

void insert(int l, int r , int c)
{
b[l] += c;
b[r+1] -= c;
}

int main()
{
scanf("%d%d",&n, &m);
for(int i = 1; i <= n; i++)scanf("%d",&a[i]);
//插入的方式形成b[i]
for(int i = 1; i <= n; i++) insert(i, i, a[i]);

while(m--)
{
int l, r ,c;
scanf("%d%d%d",&l, &r, &c);
insert(l, r, c);

}
for(int i = 1; i <= n; i++) b[i] += b[i - 1];

for(int i = 1; i <= n; i++) printf("%d ", b[i]);

return 0;
}

将a[i]\[j]表示为一个差分数列b[i]\[j]的和即可。如下所示:

算法基础(五)| 差分算法及模板详解_差分算法_03

基本思路:给其中的一个子矩阵加上一个值。矩阵以外的减去一个值即可。

可列公式表示各个范围如下:

$b[x_2 + 1][y_2 + 1] += C; $

算法基础(五)| 差分算法及模板详解_c++_07

由上面范围,可以求得最终要算的小正方形的面积公式:

矩阵的初始化;

假定a[i]\[j] = 0,b[i]\[j] =0,然后读取数组a,只需要对b进行插入即可。b[i]\[j]相当于从(i,j)到(i,j)插入一个a[i]\[j]形成的。

最后求a[i]\[j]只需要求解b[i]\[j]的前缀和即可。

例题:差分矩阵

输入一个 n 行 m 列的整数矩阵,再输入 q 个操作,每个操作包含五个整数 x1,y1,x2,y2,c,其中 (x1,y1) 和 (x2,y2)表示一个子矩阵的左上角坐标和右下角坐标。

每个操作都要将选中的子矩阵中的每个元素的值加上 c。

请你将进行完所有操作后的矩阵输出。

输入格式

第一行包含整数 n,m,q

接下来 n 行,每行包含 m 个整数,表示整数矩阵。

接下来 q 行,每行包含 5 个整数 x1,y1,x2,y2,c表示一个操作。

输出格式

共 n 行,每行 m 个整数,表示所有操作进行完毕后的最终矩阵。

数据范围

1≤n,m≤10001≤q≤100000 1≤x1≤x2≤n 1≤y1≤y2≤m −1000≤c≤1000 −1000≤矩阵内元素的值≤1000

输入样例:

3 4 3
1 2 2 1
3 2 2 1
1 1 1 1
1 1 2 2 1
1 3 2 3 2
3 1 3 4 1

输出样例:

2 3 4 1
4 3 4 1
2 2 2 2
#include<iostream>
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()
{
scanf("%d%d%d", &n, &m, &q);

for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
scanf("%d", &a[i][j]);


for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
insert(i, j, i, j, a[i][j]);

while( q-- )
{
int x1, x2, y1, 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++)
b[i][j] += b[i-1][j] +b[i][j-1] -b[i-1][j-1];

for(int i = 1; i<=n; i++)
{
for(int j = 1; j<= m; j++)
printf("%d ", b[i][j]);
puts("");
}
return 0;
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK