前缀和差分(一维+二维)

目录

1.一维前缀和

2.一维差分

一维差分数组初始化

差分标记区间

恢复原数组(一维前缀和)

3.二维前缀和

二维前缀和初始化

 子矩形的前缀和

 4.二维差分

初始化差分

标记区间

复原差分数组(对差分矩阵取前缀和)


1.一维前缀和

一维还是很好理解的,对原数组求和,前缀和每个元素表示之前所有元素的总和。累加即可

#include <bits/stdc++.h>
#include<iostream>
#include<thread>
using namespace std;
#pragma warning(disable:4996);
#define ll long long
#define	int ll
#define endl "\n"
#define rep(j,b,e) for(int j=b;j<=e;j++)
#define eb(arr) arr.begin(),arr.end()
#define mm(arr) memset(arr,0,sizeof(arr))
#define get(a,node) get<a-1>(node)
int T;
const int N = 2e5 + 10;
int n, k, q;
void inline  inln() {}
template<class T, class ...Arg>
void inline  inln(T& t, Arg&...args) {
	cin >> t;
	inln(args...);
}
void inline pln() {
	cout << "\n";
}
template <class T, class ...Arg>
void inline pln(T t, Arg ...args) {
	cout << t << " ";
	pln(args...);
}
template<typename T>
void inline pln(vector<T>& a, string s = " ") {
	for (T& x : a) {
		cout << x << s;
	}
	cout << endl;
}

signed main()
{
#ifndef ONLINE_JUDGE
	//freopen("out.txt", "w", stdout);
#endif
	inln(n, k);
	vector<int>sum(1);
	rep(j, 1, n) {
		int t;
		cin >> t;
		sum.push_back(sum[j - 1] + t);
	}
	rep(j, 1, k) {
		int l, r;
		inln(l, r);
		pln(sum[r] - sum[l - 1]);
	}
	return 0;
}

2.一维差分

 差分也就是前缀和的逆运算,多用于对区间标记O(1)的处理整个区间的操作。

一维差分数组初始化

对前缀和公式移项:

sum[i] = sum[i-1]+arr[i]  -->   arr[i] = sum[i]-sum[i-1]

数学意义就是sn-sn-1=an

此时arr被称为差分数组

可以看出差分和前缀和其实是互逆得到操作。

差分标记区间

如果要使[l,r]区间上每个元素a[i] 都加上C,

可以先得到a的差分数组b

因为a是b的前缀和,所以满足

a[i]=sum(b[1],b[2]....b[i])

如果使b[i]=b[i]+C

那么a数组中i之前的元素都没有变化,i之后的元素全部+C

这个很简单,如果使b[i]=b[i]+C,那么a[1],a[2]...a[i-1]都跟b[i]没关系,所以不改变

但是a[i],a[i+1],a[n] 因为都加上了b[i]所以值全部+C

而我们需要加C的区间为[l,r],所以令b[l]=b[l]+C,同时还需要让b[r+1]=b[r+1]-C。

看图就很好理解为什么b[r+1]需要-C了

dif[l] += c;
dif[r + 1] -= c; 

要注意差分标记时,只影响后面的原数组,二维差分也是如此。

恢复原数组(一维前缀和)

#include <bits/stdc++.h>
#include<iostream>
#include<thread>
using namespace std;
#pragma warning(disable:4996);
#define ll long long
#define	int ll
#define endl "\n"
#define rep(j,b,e) for(int j=b;j<=e;j++)
#define eb(arr) arr.begin(),arr.end()
#define mm(arr) memset(arr,0,sizeof(arr))
#define get(a,node) get<a-1>(node)
int T;
const int N = 2e5 + 10;
int n, k, q;
void inline  inln() {}
template<class T, class ...Arg>
void inline  inln(T& t, Arg&...args) {
	cin >> t;
	inln(args...);
}
void inline pln() {
	cout << "\n";
}
template <class T, class ...Arg>
void inline pln(T t, Arg ...args) {
	cout << t << " ";
	pln(args...);
}
template<typename T>
void inline pln(vector<T>& a, string s = " ") {
	for (T& x : a) {
		cout << x << s;
	}
	cout << endl;
}


signed main()
{
#ifndef ONLINE_JUDGE
	//freopen("out.txt", "w", stdout);
#endif
	inln(n, k);
	vector<int>arr(1);
	vector<int>dif(N, 0);
	rep(j, 1, n) {
		int t;
		cin >> t;
		arr.push_back(t);
		dif[j] = arr[j] - arr[j - 1];//预处理的差分数组
	}
	while (k--) {
		int l, r, c;
		inln(l, r, c);
		dif[l] += c;//标记区间
		dif[r + 1] -= c;//差分
	}
	rep(j, 1, n) {
		dif[j] += dif[j - 1];//前缀和
		cout << dif[j] << " ";
	}
	return 0;
}

3.二维前缀和

 二维前缀和更难理解一些

二维前缀和初始化

sum[i][j]的前缀和为 蓝色矩形的和

因此得出二维前缀和预处理公式

s[ i ] [ j ] = s[ i-1 ][ j ] + s[ i ] [ j-1 ] + a[ i ] [ j ] - s[ i-1 ][ j-1 ]

------------------------------

 子矩形的前缀和

因此二维前缀和的结论为:

以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和为:

s[x2, y2] - s[x1 - 1, y2] - s[x2, y1 - 1] + s[x1 - 1, y1 - 1]

#include <bits/stdc++.h>
#include<iostream>
#include<thread>
using namespace std;
#pragma warning(disable:4996);
#define ll long long
#define	int ll
#define endl "\n"
#define rep(j,b,e) for(int j=b;j<=e;j++)
#define eb(arr) arr.begin(),arr.end()
#define mm(arr) memset(arr,0,sizeof(arr))
#define get(a,node) get<a-1>(node)
int T;
const int N = 2e5 + 10;
int n, m, k, q;

template <class T, class ...Arg>
void inline pln(T t, Arg ...args);//打印一行参数
template<typename T>
void inline pln(vector<T>& a, string s = " ");
template<class T, class ...Arg>
void inline  inln(T& t, Arg&...args);//输入一行数据
//---------------------------
signed main()
{
#ifndef ONLINE_JUDGE
	freopen("out.txt", "w", stdout);
#endif
	inln(n, m, k);
	vector<vector<int>>sum(1001, vector<int>(1001, 0));
	rep(j, 1, n) {
		rep(i, 1, m) {
			int t;
			cin >> t;
			sum[j][i] = sum[j - 1][i] + sum[j][i - 1] - sum[j - 1][i - 1] + t;//预处理
		}
	}
	rep(j, 1, k) {
		int l1, r1, l2, r2;
		inln(l1, r1, l2, r2);
		int t = sum[l2][r2] - sum[l1 - 1][r2] - sum[l2][r1 - 1] + sum[l1 - 1][r1 - 1];//子矩形和
		pln(t);
	}
	return 0;
}
//--------------------------------
void inline  inln() {}
template<class T, class ...Arg>
void inline  inln(T& t, Arg&...args) {
	cin >> t;
	inln(args...);
}
void inline pln() {
	cout << "\n";
}
template <class T, class ...Arg>
void inline pln(T t, Arg ...args) {
	cout << t << " ";
	pln(args...);
}
template<typename T>
void inline pln(vector<T>& a) {
	for (T& x : a) {
		cout << x << " ";
	}
	cout << endl;
}

 4.二维差分

二维差分算是比较复杂的过程了。

初始化差分

首先是由原矩阵得到差分数组

由一维差分的概念可知,差分矩阵的元素就是前缀和的相邻差值。也就是图中间小蓝色区域。

这段值可以由红色矩阵的和加上黄色矩阵的和减去中间重复的紫色区域。

因此,设dif为差分数组,数学公式为

dif[ i ][ j ] = arr[ i ][ j ] - arr[i - 1][ j ]  - arr[i][j - 1] + arr [i - 1][j - 1]

这样就得到原始的差分数组

标记区间

标记也很复杂,但是基本逻辑和一维差分一样,某个差分元素+c,这个点对应的子矩阵元素全部+c,我们要做的不仅是(x1,y1)+c,同时还要将无关区域-c保持不变。据图就很直观了

 写成数学表达就是:

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

复原差分数组(对差分矩阵取前缀和)

据图(i,j)的前缀和可由绿色+紫色+红色-紫绿重复的区间构成

数学表达为:

dif [ i ][ j ] = dif [i - 1][ j ] + dif [ i ][j - 1] - dif [i - 1][j - 1] + dif [ i ][ j ]

#include <bits/stdc++.h>
#include<iostream>
#include<thread>
using namespace std;
#pragma warning(disable:4996);
#define ll long long
#define	int ll
#define endl "\n"
#define rep(j,b,e) for(int j=b;j<=e;j++)
#define eb(arr) arr.begin(),arr.end()
#define mm(arr) memset(arr,0,sizeof(arr))
#define get(a,node) get<a-1>(node)
int T;
const int N = 2e5 + 10;
int n, m, k, q;

template <class T, class ...Arg>
void inline pln(T t, Arg ...args);//打印一行参数
template<typename T>
void inline pln(vector<T> a, string s = " ");
template<class T, class ...Arg>
void inline  inln(T& t, Arg&...args);//输入一行数据
//----------------------------------

signed main()
{

	inln(n, m, k);
	vector<vector<int>>dif(1010, vector<int>(1010));
	auto arr = dif;
	rep(i, 1, n) {
		rep(j, 1, m) {
			int t;
			cin >> t;
			arr[i][j] = t;
            //预处理差分数组
			dif[i][j] = arr[i][j] - arr[i - 1][j] - arr[i][j - 1] + arr[i - 1][j - 1];
		}
	}
	rep(j, 1, k) {
		int x1, y1, x2, y2, c;
		inln(x1, y1, x2, y2, c);
		dif[x1][y1] += c;
		dif[x1][y2 + 1] -= c;
		dif[x2 + 1][y1] -= c;
		dif[x2 + 1][y2 + 1] += c;//差分,差分标记影响的是后面的区域
	}
	rep(i, 1, n) {
		rep(j, 1, m) {
			dif[i][j] = dif[i - 1][j] + dif[i][j - 1] - dif[i - 1][j - 1] + dif[i][j];
            //求差分数组的前缀和,复原
		}
	}
	rep(i, 1, n) {
		pln(vector<int>(dif[i].begin() + 1, dif[i].begin() + m + 1));
	}
	return 0;
}
//-------------------------------------

void inline  inln() {}
template<class T, class ...Arg>
void inline  inln(T& t, Arg&...args) {
	cin >> t;
	inln(args...);
}
void inline pln() {
	cout << "\n";
}
template <class T, class ...Arg>
void inline pln(T t, Arg ...args) {
	cout << t << " ";
	pln(args...);
}
template<typename T>
void inline pln(vector<T> a, string s) {
	for (T& x : a) {
		cout << x << s;
	}
	cout << endl;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值