运用数学思想解释前缀和的逆运算——差分、差分矩阵

运用数学思想解释差分与前缀和的关系

学习差分前先需要回顾下前缀和的概念及其定义: 点这里看前缀和概念

我们已知前缀和的代码的实现:s[ i ] = s[ i - 1] + a[ i ];(式子1)
即数组S存的是数组a的前 i 项和;这个相信很好理解,但是说到差分的定义则容易混淆;
为什么说差分是前缀和的逆运算呢?
我们看下差分定义:差分即相邻两个数的差
我们直接按照其定义字面意思能写下:s[ i ] = a[i] - a[i-1];(式子2)
即用数组S记录下数组a的每个相邻的数之间的差。

我们观察这两个式子,将前缀和中的数组s和数组a反以下即有了式子2。
即在数学中,令 s = a, a = s,则 a[i] = a[i-1] + s[i] ,将a[i-1]放另一边变得出
s[1] = a[i] - a[i-1]。这样便可以看出前缀和与差分是一种逆运算。

我们构造一个集合bn使得满足:
av = b1 + b2 + b3 + … + bv;
构造bn使得数组n称为b数组的前缀和,b数组就是a数组的差分

我们用差分能够更好的解决区间和的问题:
若给定一个数组a,若要在其中一段区间内[l,r]内加上一个数字c,我们可能第一时间会用for(int i = l ;i <= r; i++) a[i] += c; 这样时间复杂度为O(n),若区间[l,r]非常大,处理起来容易超时,因此我们用差分进行操作,s[l] += c; s[r + 1] -= c;这样保证只在区间l到r之间加上c。

最后我们还需要还原经过加完c的数组a,直接”倒推“,我们再看式子2,得出a[i]的办法,即a[i] = s[i] + a[i -1]


例题:差分
输入一个长度为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

C++实现:

#include<iostream>

using namespace std;

const int N = 100010; 

int n,m;
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]);
	
	for(int i = 1; i <= n;i ++ ) insert(i, i ,a[i]);  
//相当于	for(int i = 1; i <= n; i ++) b[i] = a[i] - a[i-1];
	
	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];  //通过b[i] = b[i] + b[i-1]分解出原数组的a[]的情况 
	for(int i = 1; i <= n;i ++ ) printf("%d ", b[i]);
	
//或者  for(int i = 1; i <= n ; i ++) a[i] = b[i]+ a[i-1];
//   	for(int i = 1; i <= n ; i++) printf("%d ",a[i] );
	return 0;
 } 

差分矩阵
输入一个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 ,
−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 n, m, q;
int 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("%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, 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;
 }

猜你喜欢

转载自blog.csdn.net/diviner_s/article/details/107350117