前缀和+差分+离散化+区间合并

题目清单

  • 前缀和
    • 一维前缀
    • 二维前缀
  • 差分
    • 一维差分
    • 二维差分
  • 离散化
  • 区间合并
  • 菜就菜呗,菜就学呗,谁开始还不是一只小白菜。 up up up!!!


前缀和

前缀和的作用是降低时间复杂度来计算一个区间的数值总和,比如计算S[l]到S[r]区间所有数值的总和,一般的方法就是一个个遍历相加,从l加到r
对于单次求和不影响,但是如果多次求和时间复杂度就会差别很大
遍历的时间复杂度是O(n);
前缀和时间复杂度是O(1);

一维前缀

说前缀和是一种算法不如说是一种公式:
S[i]=a[1]+a[2]+a[3]+…+a[i]
区间l到r的和就是a[l]+a[l+1]+…+a[r]=S[r]-S[l-1]
为了方便不用进行下标的判断,默认前缀和数据存储从下标1开始,S[0]=0;
这样a[1]+a[2]+…+a[r]=S[r]-S[0]

上代码 (代码简短又易懂)

#include
#include
#define N 1000005
using namespace std;
long long a[N], S[N];//默认S[0]=0
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 l, r;
	for (int i = 0; i < m; i++)
	{
		cin >> l >> r;
		cout << S[r] - S[l - 1] << endl;
	}
	return 0;
}

二维前缀

类似一维,主要推导两个式子,

  • 一个是用来初始化S[i][j]:
    S[i][j]=S[i-1][j]+S[i][j-1]-S[i-1][j-1]+a[i][j];
  • 一个是用来计算区域和的:
    S=S[x2][y2]-S[x2][y1-1]-S[x1-1][y2]+S[x1-1][y1-1]表示左上角为(x1,y1),右下角为(x2,y2)的一片区域的和,(x1,y1)包含在区域内

画个图就看出来了

#include
#include
#define N 1005
using namespace std;
long long a[N][N], 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][j - 1] + S[i - 1][j] - S[i - 1][j - 1] + a[i][j];
		}
	int x1, y1, x2, y2;
	for (int i = 0; i < q; i++)
	{
		cin >> x1 >> y1 >> x2 >> y2;//(x1,x2)是包含在矩阵里面的
		cout << S[x2][y2] - S[x1 - 1][y2] - S[x2][y1 - 1] + S[x1 - 1][y1 - 1] << endl;
	}
	return 0;
}

差分

什么是差分?
两个数组,b[],a[],a[]是b[]的前缀和数组,b[]是a[]的差分数组
差分就是将数列中的每一项分别与前一项数做差。
首先一个数组 :1 2 5 4 7 3
那么差分之后 :1 1 3 -1 3 -4 -3
其实就是 b [ i ] = a [ i ] - a [ i − 1 ]
a[i]=b[1]+b[2]+b[3]+…+b[i]
注意得到的差分序列第一个数和原来的第一个数一样(相当于第一个数减0)
差分序列最后比原序列多一个数(相当于0减最后一个数)
(截取自:https://blog.csdn.net/weixin_44668898/article/details/104281102)

差分一般使用场景:
给出 n 个数,再给出 m 个询问,每个询问给出 l,r,c,要求你在 l 到 r 上每一个值都加上 c,而只给你 O(n) 的时间范围,怎么办?
如果暴力,时间复杂度就是 O(n*m)
如果线段树或者树状数组,时间复杂度就是 O(mlogn)
所以这里用差分,时间复杂度就是 O(n)

一维差分

题目链接-差分
题目链接-差分矩阵

记两个关键的公式:

  • b[i]=a[i]-a[i-1];
  • a[i]=b[1]+b[2]+b[3]+…+b[i]
#include
#include
using namespace std;
int a[100005], b[100005];
int main()
{
	int n, m, l, r, c;
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
	{
		cin >> a[i];
		b[i] = a[i] - a[i - 1];//构造差分数组
	}
	while (m--)
	{
		cin >> l >> r >> c;
		b[l] += c; b[r + 1] -= c;
		//a[r+1]=b[1]+b[2]+..b[l]+c+...+b[r]+b[r+1]+c;
		//a[r]=b[1]+b[2]+..b[l]+c+...+b[r];
	}
	for (int i = 1; i <= n; i++)
	{
		a[i] = b[i] + a[i - 1];//前缀和运算
		cout << a[i] << " ";
	}
}

二维差分

给个链接,感觉讲的挺清楚的了:
差分矩阵
前缀和+差分+离散化+区间合并_第1张图片
主要记住四个点加一个公式:
四点:

  • b[x1][y1]+=c; 这一步使得从(1,1)延展开的矩阵凡是经过(x1,y1)的都+=c。
  • b[x1][y2+1]-=c; 因为子矩阵边界{x1<=x<=x2,y1<=y<=y2} 所以一旦y>y2,就不许要+=c,所以b[x1][y2+1]-=c,抵消。
  • b[x2+1][y1]-=c; 同理上一步。
  • b[x2+1][y2+1]+=c; 因为在(x2+1,y2+1)这里建了两次,+=c达到平衡
    公式:
  • a[i][j]=a[i-1][j]+a[i][j-1]-a[i-1][j-1]+b[i][j]; 这一步用在b 差分数组的构建
    ->b[i][j]=a[i][j]-a[i-1][j]-a[i][j-1]+a[i-1][j-1]

这里可以总结成子矩阵内部有多少b[i][j]加上或减上c,对应的前缀和就加上其总和。

#include
#include
using namespace std;
int a[1005][1005], b[1005][1005];
void Insert(int x1, int y1, int x2, int y2, int c);
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];
	//写法1:
	/*for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			Insert(i, j, i, j, a[i][j]);
			和下面的个写法本质一样*/
			//写法2:
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			b[i][j] = a[i][j] - a[i - 1][j] - a[i][j - 1] + a[i - 1][j - 1] ;
	int x1, y1, x2, y2, c;
	while (q--)
	{
		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][j - 1] + a[i - 1][j] - a[i - 1][j - 1] + b[i][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++)
			cout << a[i][j] << " ";
		//法二:cout<
		cout << endl;
	}
}
void Insert(int x1, int y1, int x2, int y2, int c)
{
	b[x1][y1] += c, b[x2 + 1][y2 + 1] += c;
	b[x2 + 1][y1] -= c; b[x1][y2 + 1] -= c;
}

离散化

离散化通过映射将稀疏区间变得密集,再通过前缀和求区间和
例题来源
(注意一下二分查找的写法,果然太久没用二分查找有的点竟然忘了)
关于pair的用法参考STL

重点在于离散化的处理

#include
#include
#include
#include
using namespace std;
typedef long long ll;
ll a[300005], S[300005];
vector<ll>pos;
vector<pair<ll, ll>>P1, P2;//用来存储映射关系(坐标与c+区间左右边界)
int find(ll x)
{
	int l = 0, r = pos.size() - 1,mid;
	while (l <= r)//*****是小于等于
	{
		mid = l + r >> 1;
		if (pos[mid] == x) return mid + 1;//因为要构造前缀和,所以数组下标从1开始
		else if (x < pos[mid]) r = mid - 1;
		else l = mid + 1;
	}
	//return -1;肯定是找得到边界的所以这一步不要也行
}
int main()
{
	//本题的思路:
	//通过对数据的分析x的范围远超n,m的范围,有意义的坐标一共n+2m,范围大概1-3*100000
	//可以看出数据非常离散,这里再直接用前缀和就没法实现了,所以先对数组下标进行映射,去掉那些无意义的数组下标
	//映射采用pair,first表示原数组下标,second表示添加的c,
	//离散化结束后缩短了范围,始数据变得密集,接着通过前缀和求区间和,这是一道前缀和+离散化的题目
	ll n, m, x, c, l, r;
	cin >> n >> m;

	for (ll i = 0; i < n; i++)
	{
		cin >> x >> c;
		pos.push_back(x);
		P1.push_back({ x,c });
	}
	//为了方便找到区间边界,所以再把区间边界映射存入P2
	for (ll i = 0; i < m; i++)
	{
		cin >> l >> r;
		pos.push_back(l);
		pos.push_back(r);
		P2.push_back({ l,r });//留着后面要用来查询
	}
	sort(pos.begin(),pos.end());
	pos.erase(unique(pos.begin(), pos.end()), pos.end());
	//unique函数去重,然后返回最后一个元素,这时候的就可以通过在pos确定x小标的元素在300000大小的数组里面的下标了

	for (auto x : P1)
	{
		int p = find(x.first);
		if (p != -1) a[p] += x.second;
	}
	for (int i = 1; i <= pos.size(); i++)
	{
		S[i] = S[i - 1] + a[i];
	}
	//查询
	for (auto x : P2)
	{
		l = find(x.first); r = find(x.second);
		cout << S[r] - S[l - 1] << endl;
	}
	return 0;
}

区间合并

说一下思路:先将每个区间的左右端点打包成pair类型存入vector,根据区间的左端点排序,接着进行合并,在上一个区间的beg和end不变的情况下比较上一个区间与当前区间的位置关系,一共有三种情况,新的子区间在上一个区间的内部,在上一个区间的外部,与上一个区间有交集(不可能在上一个区间的前面,因为先对所有区间的左端点进行排序过了)。当新区间的first大于未更新的end,说明两个区间没用交际,更新beg和end,将上一个区间存入re(新的pair类型的vector),如果end>=first,有两种情况,一种是有交集,一种是包含,所以只要对end进行更新,end=max(end,seg.end),前一中是包含,后一种是交集。
对于segs中所有pair处理结束后,最后一个区间还没入re数组,因为入数组的操作是当判断出现新的区间时才进行的。
所以当所有pair处理完,在对beg和end进行一次判断,如果end!=-2e9,就入数组,如果是-2e9肯一开始就是空vector。
例题链接–区间合并

#include
#include
#include
#include
using namespace std;
typedef pair<int, int> PII;
void Merge(vector<PII>&segs);
int main()
{
	int n, l, r;
	vector<PII>segs;
	cin >> n;
	for (int i = 0; i < n; i++)
	{
		cin >> l >> r;
		segs.push_back({ l,r });
	}
	Merge(segs);
	cout << segs.size();
	return 0;
}
void Merge(vector<PII>&segs)
{
    sort(segs.begin(),segs.end());
	vector<PII>re;
	int beg = -2e9, end = -2e9;
	for (auto seg : segs)
	{
		if (seg.first > end)//不包含,不想接
		{
			if (end != -2e9) re.push_back({ beg,end });
			beg = seg.first;
			end = seg.second;
		}
		else end = max(end, seg.second);
	}
	if (end != -2e9) re.push_back({ beg,end });
	segs = re;
}

关键代码:

void Merge(vector<PII>&segs)
{
    sort(segs.begin(),segs.end());//pair的排序是先比较first,后比较second
	vector<PII>re;
	int beg = -2e9, end = -2e9;
	for (auto seg : segs)
	{
		if (seg.first > end)//不包含,不想接
		{
			if (end != -2e9) re.push_back({ beg,end });
			beg = seg.first;
			end = seg.second;
		}
		else end = max(end, seg.second);
	}
	if (end != -2e9) re.push_back({ beg,end });
	segs = re;

菜就菜呗,菜就学呗,谁开始还不是一只小白菜。 up up up!!!

你可能感兴趣的:(算法)