『算法笔记』- 02 - C++ 实现:高精度 & 前缀和与差分


📘At first:一个初学算法的萌新,如果文中有误,还请指正🤓
🎗️专栏介绍:本专栏目前基于AcWing算法基础课进行笔记的记录,包括及课上大佬讲的一些算法的模板还有自己的一些心得和理解
🕶️个人博客地址:https://blog.csdn.net/m0_73352841?spm=1010.2135.3001.5343

一、高精度

1.1 高精度加法

一般来说高精度考四种(浮点数的基本用不上):

  • 两个大的整数(位数 <=10 ^ 6,极端情况)相加
  • 两个大的整数相减
  • 一个大整数(位数 <=10 ^ 6)乘以一个小整数(数值<=10 ^ 5,大致)
  • 一个大整数除以一个小整数

以下以两个数均为正数的情况进行讨论

问题:
Q1:大整数在代码中是如何表示的?
A1:把每一位存在数组里

Q2:高位在前还是低位在前?
A2:第0位存个位,因为在计算的时候可能会进位,这时就需要在高位上补1,在数组的末尾补充是比较方便的

在这里插入图片描述

e.g.

1.1.1 代码实现

#include <iostream>
#include <vector>

using namespace std;

vector<int> add(vector<int>& A, vector<int>& B)
//算数组表示的整数A和数组表示的整数B,返回值是数组表示的整数C
//加引用是为了提高效率,如果不加应用它就会把整个数组copy一遍,如果加上引用就不会copy整个数组了

{
	vector<int> C;

	int t = 0;
	//记录A与B每一位相加的中间变量

	for (int i = 0; i < A.size() || i < B.size(); i++)
	{
		if (i < A.size()) t += A[i];
		if (i < B.size()) t += B[i];
		//用if的原因是A或B可能在相同位上有一者为0

		C.push_back(t % 10);
		//对t进行取余,作为数组C的低位,填入

		t /= 10;
		//进行进位

	}

	if (t) C.push_back(1);
	return C;
}

int main()
{
	string a, b;
	vector<int> A, B;

	cin >> a >> b;
	for (int i = a.size() - 1; i >= 0; i -- ) A.push_back(a[i] - '0');
	//将字符转换成数字再倒置重新储存
	
	for (int i = b.size() - 1; i >= 0; i -- ) B.push_back(b[i] - '0');

	auto C = add(A, B);
	//auto,编译器会自己推断这个变量是什么类型的
	//auto C等价于vector<int> C

	for (int i = C.size() - 1; i >= 0; i--) printf("%d", C[i]);
	return 0;
}

1.1.2 图示

以下面的数据为例

85129
65247

如图所示,A、B两组数字已录入。从个位开始,该位上A、B中都有数,所以t = A[i] + B[i] + t,即t = 9 + 7 + 0 = 16

对t进行取余,并将其填入C中,即C.push_back(t % 10)(图中那个跟加号类似的东西其实是tQWQ)

在这里插入图片描述

e.g.

然后对t进行进位操作,即t /= 10

在这里插入图片描述

e.g.

十位上的操作,这时t已经进位了,一开始就有一个1,所以t = 1 + 2 + 4 = 7,将其导入C中

在这里插入图片描述

e.g.

t进行进位,由于未大于10,所以t = t / 10 = 0

在这里插入图片描述

e.g.

以此类推,最后得到的结果为150376,最后倒着输出即可(因为我们表示数的大小是高位在前)

在这里插入图片描述

e.g.

计算器验证🤓

 
在这里插入图片描述

e.g.

1.2 高精度减法

两数相减,要保证前者大于等于后者,即A >= B,如果小于则算B - A,再加负号,这样就能保证每次都是较大的数减较小的数,一定不会出现负数的情况,最高位不会再向前借位,少处理边界情况

在这里插入图片描述

总体情况

在这里插入图片描述

A、B两组数,每个位上的运算情况

以下均已两数为正数的情况进行讨论。如果运算中有负数,要判断第一个录入的字符是不是负号。另,两个数相减的话一定能转换成两个数的绝对值进行相减

1.2.1 代码实现

#include <iostream>
#include <vector>

using namespace std;

bool cmp(vector<int> &A, vector<int> &B)
//cmp函数要自己实现,判断是否有 A >= B,是的话返回true,否则返回flase

{
	if (A.size() != B.size()) return A.size() > B.size();
	//首先判断位数,如果位数不同,A的位数大就大,反之亦然

	for (int i = A.size() - 1; i >= 0; i--)
		//从高位至低位逐一比较

		if (A[i] != B[i])
			return A[i] > B[i];

	return true;
}

vector<int> sub(vector<int>& A, vector<int>& B)
{
	vector<int> C;
	for (int i = 0, t = 0; i < A.size(); i++)
	//这里一定保证是“A” >= “B”的,“A”的size一定大于“B”的size

	{
		t = A[i] - t;
		if (i < B.size()) t -= B[i];
		//判断B有没有这一位

		C.push_back((t + 10) % 10);
		//这一步解析请看图示部分

		if (t < 0) t = 1;
		//进位,下一位再减的时候就要多减一个1了,因为上一位借了一个

		else t = 0;
	}

	while (C.size() > 1 && C.back() == 0) C.pop_back();
	//这一步解析请看图示部分

	return C;
}

int main()
{
	string a, b;
	vector<int> A, B;

	cin >> a >> b;
	for (int i = a.size() - 1; i >= 0; i--) A.push_back(a[i] - '0');
	

	for (int i = b.size() - 1; i >= 0; i--) B.push_back(b[i] - '0');

	if (cmp(A, B))
		//判断两个大整数谁大

	{
		auto C = sub(A, B);
		//如果A大于B就直接算

		for (int i = C.size() - 1; i >= 0; i--) printf("%d", C[i]);
	}

	else
	{
		auto C = sub(B, A);

		printf("-");
		//输出一个负号
		for (int i = C.size() - 1; i >= 0; i--) printf("%d", C[i]);
	}

	auto C = sub(A, B);

	
	return 0;
}

1.2.2 图示

以下面的数据为例

56823
34987

将数字录入后开始进行每个位上的相减运算,从个位开始t = A[i] - B[i] - t,即t = 3 - 7 - 0 = -4。这时候返回给C的数,也即我们前面的代码C.push_back((t + 10) % 10 )

在这里插入图片描述

e.g.

所以返回给C的是6。这时,由于t = -4 < 0,所以赋给t = 1,也即我们的进位操作。下一次,A对应的位就要多减1了

在这里插入图片描述

e.g.

在这里插入图片描述

e.g.

十位上的操作,这时t已经进位了,一开始就有一个1,所以t = 2 - 1 - 8 = -7,将其导入C中( C.push_back((t + 10) % 10)

在这里插入图片描述

e.g.

t进行进位,由于未大于0,所以t = 1

在这里插入图片描述

e.g.

以此类推,最后得到的结果为21836,最后倒着输出即可(因为我们表示数的大小是高位在前)

在这里插入图片描述


计算器验证🤓

 

在这里插入图片描述

e.g.

关于while (C.size() > 1 && C.back() == 0) C.pop_back()这步操作,是为了去掉前导的0

在这里插入图片描述

e.g.

 

如图中这里例子,运算完录入我们C中的数据就是1 0 0,这两个0便是前导0,我们要出去它们,所以便有了这步操作


1.3 高精度乘法

以下图为例,A0~A5表示每一位,b表示那个较小整数。注意,我们这里是拿整个b去和A中的每一位去乘的

个位上的数应当是(A0 * b)取10的余数,即,而进位则是(A0 * b) / 10。同理,十位上的数就是(A1 * b + t ) % 10,进位为(A1 * b + t) / 10,这里t代指(A1 * b + t) / 10,即进位

在这里插入图片描述

e.g.

1.3.1 代码实现

#include <iostream>
#include <vector>

using namespace std;

vector<int> mul(vector<int> &A, int b)
{
	vector<int> C;

	int t = 0;
	//最开始的进位是没有的,为0

	for (int i = 0; i < A.size() || t ; i++)
	//要么是i没有循环完,要么是t不为0,只要t不是0就一直做,把最后一次也包含了

	{
		if (i < A.size()) t += A[i] * b;
		//判断i是不是在A的范围里
		//上一位的进位加上这一位和b的积

		C.push_back(t % 10);
		//当前这一位的结果

		t /= 10;
		//进位
	}
	
	return C;
}

int main()
{
	string a;
	//a很长,用字符串来存

	int b;
	//b很短,用int来存

	cin >> a >> b;

	vector<int> A;

	for (int i = a.size() - 1; i >= 0;  i-- ) A.push_back(a[i] - '0');
	//从高位转换

	auto C = mul(A, b);

	for (int i = C.size() - 1; i >= 0; i -- ) printf("%d", C[i]);
}

1.3.2 图示

以下面的数据为例

5398
21

首先t = 8 * 21 = 168,个位上的数就是t % 10 = 8

在这里插入图片描述

e.g.

t进行进位,即t /= 10 = 16

在这里插入图片描述

e.g.

再确定十位上的数字,t = (9 * 21 + 16) = 205,则十位上的数为t % 10 = 5

在这里插入图片描述

e.g.

t进行进位,即t /= 10 = 20

在这里插入图片描述

e.g.

以此类推,最后得到的结果为113358,最后倒着输出即可(因为我们表示数的大小是高位在前)

在这里插入图片描述

e.g.

计算器验证🤓

 

在这里插入图片描述

e.g.

1.4 高精度除法

以下图为例模拟运算过程。A0~A3表示A中的每一位,b表示那个较小的整数

最开始余数就是A3(见代码实现部分的具体解析),我们用r表示。拿他去除以b,商用C3表示。余数A3模b等价于A3 - b * C3,即下一位的余数,我们用r'表示。由于到了下一位,上一位的余数就需要先乘以10再做除法,即r' * 10 + A2。再把这当作一个整体除以b,得到商C2,以此类推进行运算

在这里插入图片描述

e.g.

1.4.1 代码实现

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

vector<int> div(vector<int>& A, int b, int &r)
//除了返回商(用C表示),还会返回一个余数,用r表示,是引用

/*除法是从最高位开始算的,之前的加、减、乘都是从最低位开始算的,如果只有除法的话,正着存会好一些
但是一般的题目里几种运算是混合的,所以这里也倒着来存,但是运算我们是从最高位开始算的*/

{
	vector<int> C;

	r = 0;
	//一开始余数是0
	
	for (int i = A.size() - 1; i >= 0; i--)
	{
		r = r * 10 + A[i];
		//每一次余数等于余数乘以10加上这一位
		C.push_back(r / b);
		r %= b;

	}

	reverse(C.begin(), C.end());
	//由于我们开头说的,此时C中下标0位置上存的是最高位,要把它们逆过来

	while (C.size() > 1 && C.back() == 0) C.pop_back();

	return C;
}

int main()
{
	string a;
	//a很长,用字符串来存

	int b;
	//b很短,用int来存

	cin >> a >> b;

	vector<int> A;

	for (int i = a.size() - 1; i >= 0; i--) A.push_back(a[i] - '0');
	//从高位转换

	int r;
	auto C = div(A, b, r);

	for (int i = C.size() - 1; i >= 0; i--) printf("%d", C[i]);
	cout << endl << r << endl;
	//第一行输出商,第二行输出余数

	return 0;
}

1.4.2 图示

我们以下面的数据为例

7521
65

一开始r = 0,先进行最高位上的运算,r = r * 10 + A[i],即r = 0 * 10 + 7 = 7,然后返回给C的数是7 / 65 = 0

在这里插入图片描述

e.g.

然后r %= b = 7仍为7,为下一位运算做准备

在这里插入图片描述

e.g.

下一位,r = r * 10 + A[i] = 75,返回给C的数为75 / 65 = 1

在这里插入图片描述

e.g.

然后r %= b = 10

在这里插入图片描述

e.g.

以此类推,最后得到的结果是商为115,余数为46

在这里插入图片描述

e.g.

计算器验证🤓

 

在这里插入图片描述

这样可能不太好看,这样算115 * 65 = 7475,然后它们的差7521 - 7475 ,就是46

二、前缀和与差分

前缀和与差分是一对逆运算

2.1 前缀和

2.1.1 一维前缀和

如果我们有一个长度为n的数组,Si表示原数组中前i数的和,这就是前缀和。前缀和中一定要让下标从1开始(后面解释)

在这里插入图片描述

e.g.

问题
Q1:Si如何求?
A1:大致实现过程:

for (i = 1; i <= n; i ++ )
s[i] = s[i - 1] + a[i]

S[i - 1]是a数组里面前i-1个数的和,加上第i个数,就可以得到前i个数的和了,就是Si 。还有边界S0要定义成0(后面解释)

Q2:前缀和数组的作用是什么?
A2:能快速的求出原数组中一段的和。比如要求原数组中从lr这一段数组的和,如果没有前缀和数组,就需要循环一遍,再做运算。而有了前缀和数组从lr这一段数的和就是Sr - Sl-1 。只需要一次运算就可以算出任意区间的和,这是前缀和最大的应用,基本上也是唯一的应用

Q3:为什么下标要从1开始、要定义S0为0?
A3:其实下标从1开始就是为了能定义出S0 。从1开始S0就不需要对应任何变量了,就可以把它定义为0

这有什么好处?主要是为了处理边界,比如要求[1,10]这段和,就要算S10 - S0,那我们就需要用到S0这个东西

我们为了统一,让所有lr都用这样一个同样的公式,那我们就把S0定义成0(那其实上个例子就是在求S10),也就是可以少考虑一个特殊判断


2.1.1.1 代码实现

下面以一道例题为例

题目描述
输入一个长度为n的整数序列

接下来再输入m个询问,每个询问输入一对lr

对于每个询问,输出原序列中从第l个数到第r个数的和。

输入格式
第一行包含两个整数nm

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

接下来m行,每行包含两个整数lr,表示一个询问的区间范围。

输出格式
共m行,每行输出一个询问的结果。

数据范围
1 <= l <= r <= n,
l <= n, m <= 100000,
-1000 <= 数列中元素的值 <= 1000

输入样例:

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

输出样例:

3
6
10

完整代码

#include <iostream>

using namespace std;

const int N = 100010;

int n, m;
//n为数组内元素个数
//m为询问个数

int a[N], s[N];
//a[N]为原数组
//s[N]为前缀和数组

int main()
{
	scanf_s("%d%d", &n, &m);
	/*
	由于我用的Visual Studio的版本比较新,scanf被认为是不安全的
	可能造成数据溢出,就只好换成scanf_s()
	*/

	for (int i = 1; i <= n; i++) scanf_s("%d", &a[i]);
	//注意这里也是从1开始给数组a赋值的
	
	for (int i = 1; i <= n; i++)s[i] = s[i - 1] + a[i];
	//前i个数的和等于前i-1个数加上第i的数
	
	while (m -- )
	{
		int l, r;
		scanf_s("%d%d", &l, &r);
		//输入的数比较大,用scanf,比cin快差不多1倍
		

		printf("%d\n", s[r] - s[l - 1]);
		//前r个数的和减去前l-1个数的和,就等于l到r的和
	}

	return 0;
}

2.1.1.2 图示

其实这里有一个地方要说明,就是:
for (int i = 1; i <= n; i++)s[i] = s[i - 1] + a[i];
注意,这里是对数组s中每一位上赋予前面所有项的和。其实每轮循环输出数组s每一位上的值就是:

在这里插入图片描述

注:当数组在全局变量区定义时,默认初始值为0,即s[0] = 0;两数组中下标都是从“1”开始赋值的

2.1.2 二维前缀和

如果想快速求出一个子矩阵的和,也可以用前缀和的思想来做。如图,如果小矩形和虚线矩形的交点是aij的话,那这个虚线围起来的所有元素的和就是Sij

在这里插入图片描述

e.g.

那么小矩形中的元素和该怎么求?

我们用坐标分别表示小矩形的两个坐标,用Sx2y2表示虚线矩形中的元素和

那么小矩形中元素和就等于图中虚线矩形的元素和(Sx2y2)减去绿色矩形中的元素和(Sx2y1-1)、红色矩形中的元素和(Sx1-1y2),再加上重叠减去的部分(Sx1-1y1-1),即:

Sx2y2 - Sx2y1-1 - Sx1-1y2 + Sx1-1y1-1

在这里插入图片描述

e.g.

问题:

Q1:如何把aij(aka x2,y2)算出来?
A1二重循环,第一个循环i从1至n, 第二个循环j从1至m

Q2:怎么算Sij(aka Sx2y2)?
A2:

Sij = Sij-1 + si-1j - Si-1j-1 + aij


2.1.2.1 代码实现

下面以一道题为例

Q:

题目描述
输入一个nm列的整数矩阵,再输入q个询问,每个询问包含四个整数x1y1x2y2,表示一个子矩阵的左上角坐标和右下角坐标

对于每个询问输出子矩阵中所有数的和

输入格式
第一行包含三个整数nmq

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

接下来q行,每行包含四个整数x1y1x2y2,表示一组询问

输出格式
q行,每行输出一个询问的结果

数据范围
1 <= n,m <= 1000,
1 <= q <= 100000,
1 <= x1 <= x2 <= n,
1 <= y1 <= y2 <= m,
-1000 <= 矩阵内元素的值 <= 1000

输入样例:

3 4 3
1 7 2 4
3 6 2 8
2 1 2 3
1 1 2 2
2 1 3 4
1 3 3 4

输出样例:

17
27
21

完整代码

#include <iostream>

const int N = 1010;

int n, m, q;
//n、m边长
//q是询问次数

int a[N][N], s[N][N];
//a[N][N]为原数组
//s[N][N]为前缀和数组

int main()
{
	scanf_s("%d%d%d", &n, &m, &q);
	for (int i = 1; i <= n; i ++ )
		for (int j = 1; j <= m; j ++ )
			scanf_s("%d", &a[i][j]);
	//将数录入矩阵中

	for (int i = 1; i <= n; i ++ )
		for (int j = 1; j <= m; j ++ )
			s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];
	//求前缀和,即Sij

	while (q -- )
	//m个询问

	{
		int x1, y1, x2, y2;
		scanf_s("%d%d%d%d", &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;
}

2.1.2.2 图示

我们先把样例矩阵画出来

在这里插入图片描述

原数组

然后画出前缀和数组

在这里插入图片描述

前缀和数组

然后看第一组坐标(1,1),(2,2),对应我们前缀和中的17

在这里插入图片描述

(1,1),(2,2)

 
其余的类似,比如第二组坐标(2,1),(3,4)就是41 - 14 = 27


2.2 差分

2.2.1 一维差分

构造使得a数组是b数组的前缀和,b数组就成为a数组的差分,a就是b的前缀和

在这里插入图片描述

e.g.

在这里插入图片描述

构造

问题:
Q1:差分有什么用?
A1:假设已经构造出b数组,如果想求原数组就只需要对b数组求一遍前缀和就可以求出原数组

前缀和主要是能帮助我们快速处理一种操作

定一个区间[l,r],让我们在a数组的这个区间内所有的数加上一个C(al+C、al+1+C、…、ar+C ),这些操作完成后,如果想求原的a数组的话,只需要把b数组扫描一遍,求一下b数组的前缀和,就可以求出原来的a数组

Q2:当我们对a数组这样操作后(给定区间加C),对B数组会有什么影响?
A2:全部加C,a数组又是b数组的前缀和,因此,就要让bl+C,效果就是,算al的时候,从b1一直加到bl

bl+C,al也会自动加上C。但是我们只要lr这个区间内加上C,r+1后的数不能加上C,所以就让br+1-C就好了

主要作用就是可以给原数组中间的某个区间全部加上一个固定的值

在这里插入图片描述

e.g.

2.2.1.1 代码实现

下面以一道题为例

题目描述
输入一个长度为n的整数序列

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

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

输入格式
第一行包含两个整数nm

第二行包含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 n, m;
//n为数组内元素个数
//m为操作个数

int a[N], b[N];
//a[N]是原数组
//b[]为差分数组

void insert(int l, int r, int c)
//构造插入函数

{
	b[l] += c;
	b[r + 1] -= c;
}

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

	for (int i = 1; i <= n; i ++ ) insert(i, i, a[i]);
	//构造差分数组

	while ( m-- )
	//m个操作

	{
		int l, r, c;
		scanf_s("%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;
}

2.2.1.2 图示

先将数录入原数组中

在这里插入图片描述

原数组

然后是构造分差数组b,即b[l] += c; b[r + 1] -= c;,(这时原矩阵就是差分矩阵的前缀和了)

在这里插入图片描述

差分数组

第一步操作,根据之前的分析,差分数组中只要在一处增加,最后的前缀和都会增加,所以其实只需要在第一个位置上加1和r + 1处减1就可以了,即b[l] += c; b[r + 1] -= c;

在这里插入图片描述

差分数组

然后进行完剩下两步操作得到的差分数组为:

在这里插入图片描述

差分数组

然后把数组b变成自己的前缀和输出,即for (int i = 1; i <= n; i ++ ) b[i] += b[i - 1];

在这里插入图片描述

前缀和

2.2.2 二维差分

假设原矩阵是aij,对其构造差分矩阵bij。构造也是一样的,就是满足原矩阵是差分矩阵的前缀和就行

在一维差分中是在一段上加上一个值,而在二维差分中是在一个子矩阵中加上一个值

如图,bx1y1 += c 的效果就是把整个虚线围成矩形都加上C。但是“L”形中的部分也多加了C

那就需要减去绿色的部分,即bx2+1y1 -= c,还有红色部分,即bx1y2+1 -= c,再加上多减的(红绿重叠部分),即b~x2+1y2+1 += c

在这里插入图片描述

前缀和

2.2.2.1 代码实现

下面以一道题为例

题目描述
输入一个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 <= 1000,
1 <= q <= 100000,
1 <= x1 <= x2 <= n,
1 <= y1 <= y2 <= m,

输入样例:

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 n, m, q;
//n、m为边长
//m是操作个数

int a[N][N], b[N][N];
//a[N][N]为原矩阵
//b[N][N]为差分矩阵

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_s("%d%d%d", &n, &m, &q);

	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			scanf_s("%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 -- )
	//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 ++ )
			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;
}

2.2.2.2 图示

先将数录入矩阵中

在这里插入图片描述

原矩阵

然后是构造分差矩阵b(这时原矩阵就是差分矩阵的前缀和了),即:

b[x1][y1] += c;
b[x2 + 1][y1] -= c;
b[x1][y2 + 1] -= c;
b[x2 + 1][y2 + 1] += c;

在这里插入图片描述

差分矩阵

然后进行三步操作,最后有差分矩阵b:

在这里插入图片描述

差分矩阵

然后把差分矩阵b变成自己的前缀和输出,即:

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];

在这里插入图片描述

差分矩阵

Ending

在这里插入图片描述

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Rainbow_Criss

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值