树状数组的模板题

【如果你不知道什么是树状数组:请点这里!!!

#130. 树状数组 1 :单点修改,区间查询

这是一道模板题。
给定数列\(a_1,a_2,\dots,a_n\),你需要进行m各操作,操作有两类:

  • \(1\) \(i\) \(x\) :给定\(i,x\),将\(a_i\)加上\(x\)
  • \(2\) \(l\) \(r\) :给定\(l.r\),求\(\Sigma ^r _{i=l}a_i\)的值(换言之,求\(a_l+a_{l+1}+\dots+a_r\)的值)。

输入格式

第一行包含\(2\)个正整数\(n,m\),表示数列长度和询问个数。保证\(1\leq n,m\leq 10^6\)
第二行\(n\)个整数\(a_1,a_2,\dots,a_n\),表示初始数列。保证\(|a_i|\leq 10^6\)
接下来\(m\)行,每行一个操作,为以下两种之一:

  • \(1\) \(i\) \(x\) :给定\(i,x\),将\(a_i\)加上\(x\)
  • \(2\) \(l\) \(r\) :给定\(l.r\),求\(\Sigma ^r _{i=l}a_i\)的值。

保证\(1\leq l\leq r \leq n,|x|\leq 10^6\)

输出格式

对于每个\(2\) \(l\) \(r\) 操作输出一行,每行有一个整数,表示所求的结果。

思路:这是一道简单的一维树状数组的模板题

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define endl '\n'
#define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
#define _for(i, a, b) for (int i=(a); i<=(b); i++)
const int INF = 0x7fffffff;
const int MAXN = 1e6 + 10;

ll tree[MAXN], n, m;

inline ll lowbit(ll x){return x & (-x);}

void updata(ll x, ll d){
	while(x <= n){
		tree[x] += d;
		x += lowbit(x);
	}
}

ll getsum(ll x){
	ll res = 0;
	while(x > 0){
		res += tree[x];
		x -= lowbit(x);
	}
	return res;
}

int main(){
	IOS
	cin >> n >> m;
	_for(i, 1, n){
		ll a;
		cin >> a;
		updata(i,a);
	}
	while(m--){
		ll id, x, y;
		cin >> id >> x >> y;
		if(id == 1){
			updata(x,y);
		}
		else if(id == 2){
			cout << getsum(y) - getsum(x-1) << endl;
		}
	}
	return 0;
}

#131.树状数组2:区间修改,单点查询

这是一道模板题。
给定数列\(a_1,a_2,\dots,a_n\),你需要进行m各操作,操作有两类:

  • \(1\) \(l\) \(r\) \(x\):给定\(l,r,x\),对于所有\(i\in [l,r]\),将\(a_i\)加上\(x\)(换言之,求\(a_l,a_{l+1},\dots a_r\)分别加上\(x\));
  • \(2\) \(i\):给定\(i\),求\(a_i\)的值。

输入格式

第一行包含\(2\)个正整数\(n,m\),表示数列长度和询问个数。保证\(1\leq n,m\leq 10^6\)
第二行\(n\)个整数\(a_1,a_2,\dots,a_n\),表示初始数列。保证\(|a_i|\leq 10^6\)
接下来\(m\)行,每行一个操作,为以下两种之一:

  • \(1\) \(l\) \(r\) \(x\):对于所有\(i\in [l,r]\),将\(a_i\)加上\(x\)
  • \(2\) \(i\):给定\(i\),求\(a_i\)的值。

保证\(1\leq l\leq r \leq n,|x|\leq 10^6\)

输出格式

对于每个\(2\) \(i\) 操作输出一行,每行有一个整数,表示所求的结果。

思路:这同样也是一道模板题,对于区间修改,我们首先会想到的是差分,没错,我们可以先通过差分(这里用\(arr\)数组表示)处理,将这道题转换成第一题。这样我们可以通过求\(arr[i]\)的前缀和查询。对于修改操作,只需要将\(arr[l]\)加上\(x\)\(arr[r+1]\)减去\(x\)即可。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define endl '\n'
#define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
#define _for(i, a, b) for (int i=(a); i<=(b); i++)
const int INF = 0x7fffffff;
const int MAXN = 1e6 + 10;
const int N = 1e5+10;

ll tree[MAXN], n, m;

inline ll lowbit(ll x){return x & (-x);}

void updata(ll x, ll d){
	while(x <= n){
		tree[x] += d;
		x += lowbit(x);
	}
}

ll getsum(ll x){
	ll res = 0;
	while(x > 0){
		res += tree[x];
		x -= lowbit(x);
	}
	return res;
}

int main(){
	IOS
	int a, b = 0; 
	cin >> n >> m;
	_for(i, 1, n){
		cin >> a;
		updata(i,a - b);//差分处理
		b = a;
	}
	while(m--){
		ll id, x, y;
		cin >> id;
		if(id == 1){
			int l, r, c;
			cin >> l >> r >> c;
			updata(l,c);
			updata(r+1,-c);
		}
		else if(id == 2){
			int i;
			cin >> i;
			cout << getsum(i) << endl;//单点查询
		}
	}
	return 0;
}

#132.树状数组3:区间修改,区间查询

这是一道模板题。
给定数列\(a_1,a_2,\dots,a_n\),你需要进行m各操作,操作有两类:

  • \(1\) \(l\) \(r\) \(x\):给定\(l,r,x\),对于所有\(i\in [l,r]\),将\(a_i\)加上\(x\)(换言之,求\(a_l,a_{l+1},\dots a_r\)分别加上\(x\));
  • \(2\) \(l\) \(r\) :给定\(l.r\),求\(\Sigma ^r _{i=l}a_i\)的值(换言之,求\(a_l+a_{l+1}+\dots+a_r\)的值)。

输入格式

第一行包含\(2\)个正整数\(n,m\),表示数列长度和询问个数。保证\(1\leq n,m\leq 10^6\)
第二行\(n\)个整数\(a_1,a_2,\dots,a_n\),表示初始数列。保证\(|a_i|\leq 10^6\)
接下来\(m\)行,每行一个操作,为以下两种之一:

  • \(1\) \(l\) \(r\) \(x\):对于所有\(i\in [l,r]\),将\(a_i\)加上\(x\)
  • \(2\) \(l\) \(r\) :给定\(l.r\),求\(\Sigma ^r _{i=l}a_i\)的值。

保证\(1\leq l\leq r \leq n,|x|\leq 10^6\)

输出格式

对于每个\(2\) \(l\) \(r\) 操作输出一行,每行有一个整数,表示所求的结果。

思路:看到这样的问题,我们都会头大(没思路,烦死了!!!),我们要怎么样去求呢?我们可以基于第二题的差分思想,考虑一下如何在问题二构建的树状数组上求前缀和:(在这里\(a[i]\)表示原数组,\(b[i]\)表示前缀和数组)
求位置\(y\)的前缀和,我们可以推导出这样一个式子:\(\Sigma ^ y _{i=1} a[i]=\Sigma ^y _{i=1}\Sigma ^i _{j=1} b[j]=\Sigma ^y _{i=1}d[i]*(y-i+1)=(y+1)\Sigma ^y _{i=1}d[i]-\Sigma ^y _{i=1}d[i]*i\)
这样我们至于要维护两个数组的前缀和:
一个是\(tree[i]\Sigma ^y _{i=1}d[i]\),另一个是\(tree1[i]=\Sigma ^y _{i=1}d[i]*i\)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define endl '\n'
#define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
#define _for(i, a, b) for (int i=(a); i<=(b); i++)
const int INF = 0x7fffffff;
const int MAXN = 1e6 + 10;
const int N = 1e5+10;

ll tree[MAXN], tree1[MAXN], n, m;

inline ll lowbit(ll x){return x & (-x);}

void updata(ll x, ll d){
	for(int i = x; i <= n; i += lowbit(i)){
		tree[i] += d;
		tree1[i] += d * x;
	}
}

ll getsum(ll x){
	ll res = 0;
	for(int i = x; i > 0; i -= lowbit(i)){
		res += (x + 1) * tree[i] - tree1[i];
	}
	return res;
}

int main(){
	IOS
	int a, b = 0; 
	cin >> n >> m;
	_for(i, 1, n){
		cin >> a;
		updata(i,a - b);
		b = a;
	}
	while(m--){
		ll id, x, y;
		cin >> id;
		if(id == 1){
			int l, r, c;
			cin >> l >> r >> c;
			updata(l,c);
			updata(r+1,-c);
		}
		else if(id == 2){
			int l, r;
			cin >> l >> r;
			cout << getsum(r) - getsum(l-1) << endl;
		}
	}
	return 0;
}

#133.二维树状数组1:单点修改,区间查询

给定一个\(n\times m\)的零矩阵\(A\),你需要完成如下操作:

  • \(1\) \(x\) \(y\) \(k\):表示元素\(A_{x,y}\)自增\(k\)
  • \(2\) \(a\) \(b\) \(c\) \(d\):表示询问左上角为\((a,b)\),右下角为\((c,d)\)的子矩阵内所有数的和。

输入格式
输入的第一行有两个正整数\(n,m\)
接下来若干行,每行一个操作,直到文件结束。
输出格式
对于每个\(2\)操作,输出一个整数,表示对于这个操作的回答。
对于全部数据,\(1\leq n,m\leq 2^{12},1\leq x,a,c\leq n,1\leq y, b,d\leq m,|k|\leq 10^5\),保证操作数目不超过\(3 \times 10^5\),且询问的子矩阵存在。

思路:这是一道简单的二维树状数组的模板题

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define endl '\n'
#define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
#define _for(i, a, b) for (int i=(a); i<=(b); i++)
const int INF = 0x7fffffff;
const int MAXN = 1e4 + 10;

ll n, m, tree[MAXN][MAXN];

inline int lowbit(int x){return x & (-x);}

void updata(ll x,ll y, ll d){
	for(ll i = x; i <= n; i += lowbit(i)){
		for(ll j = y; j <= m; j += lowbit(j)){
			tree[i][j] += d;
		}
	}
}

ll getsum(ll x,ll y){
	ll res = 0;
	for(ll i = x; i > 0; i -= lowbit(i)){
		for(ll j = y; j > 0; j -= lowbit(j)){
			res += tree[i][j];
		}
	}
	return res;
}

int main(){
	ll id, x, y, k, a, b, c, d;
	cin >> n >> m;
	while(scanf("%lld", &id) != EOF){
		if(id == 1){
			cin >> x >> y >> k;
			updata(x,y,k); 
		}
		else if(id == 2){
			cin >> a >> b >> c >> d;
			cout << getsum(c,d) - getsum(c,b-1) - getsum(a-1,d) + getsum(a-1,b-1) << endl;//这一步就是二维前缀和的求和
		}
	}
	return 0;
}

#134.二维树状数组2:区间修改,单点查询

给出一个\(n\times m\)的零矩阵\(A\),你需要完成如下操作:

  • \(1\) \(a\) \(b\) \(c\) \(d\) \(k\):表示左上角为\((a,b)\),右下角为\((c,d)\)的子矩阵内所有元素都加上\(k\)
  • \(2\) \(x\) \(y\):表示询问元素\(A_{x,y}\)自增的值。

输入格式
输入的第一行有两个正整数\(n,m\)
接下来若干行,每行一个操作,直到文件结束。
输出格式
对于每个\(2\)操作,输出一个整数,表示对于这个操作的回答。
对于全部数据,\(1\leq n,m\leq 2^{12},1\leq x,a,c\leq n,1\leq y, b,d\leq m,|k|\leq 10^5\),保证操作数目不超过\(3 \times 10^5\),且询问的子矩阵存在。

思路:这道题的思想很简单,二维区间修改,我们首先想到的就是二维差分,然后在使用树状数组进行单点查询即可。

#include <bits/stdc++.h>
#include <queue>
using namespace std;
typedef long long ll;
#define endl '\n'
#define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
#define _for(i, a, b) for (int i=(a); i<=(b); i++)
const int INF = 0x7fffffff;
const int MAXN = 5e5 + 10;

ll n, m, tree[10000][10000];

inline int lowbit(int x){
	return x & (-x);
}

void updata(int x, int y, int d){
	for(int i = x; i <= n; i += lowbit(i)){
		for(int j = y; j <= m; j += lowbit(j)){
			tree[i][j] += d;
		}
	}
}

ll getsum(int x, int y){
	ll res = 0;
	for(int i = x; i > 0; i -= lowbit(i)){
		for(int j = y; j > 0; j -= lowbit(j)){
			res += tree[i][j];
		}
	}
	return res;
}

int main(){
	int id, a, b, c, d, x, y, k;
	cin >> n >> m;
	while(scanf("%d", &id) != EOF){
		if(id == 1){
			cin >> a >> b >> c >> d >> k;
			updata(a,b,k);
			updata(a,d+1,-k);
			updata(c+1,b,-k);
			updata(c+1,d+1,k);
		}
		else if(id == 2){
			cin >> x >> y;
			cout << getsum(x,y) << endl;
		}
	}
	return 0;
}

#135.二维树状数组3:区间修改,区间查询

给出一个\(n\times m\)的零矩阵\(A\),你需要完成如下操作:

  • \(1\) \(a\) \(b\) \(c\) \(d\) \(k\):表示左上角为\((a,b)\),右下角为\((c,d)\)的子矩阵内所有元素都加上\(k\)
  • \(2\) \(a\) \(b\) \(c\) \(d\):表示询问左上角为\((a,b)\),右下角为\((c,d)\)的子矩阵内所有数的和。

输入格式
输入的第一行有两个正整数\(n,m\)
接下来若干行,每行一个操作,直到文件结束。
输出格式
对于每个\(2\)操作,输出一个整数,表示对于这个操作的回答。
对于全部数据,\(1\leq n,m\leq 2048,|k|\leq500\),保证操作数目不超过\(2 \times 10^5\),保证运算过程中及最终结果均不超过\(64\)位带符号整数类型的表示范围,并且修改与查询的子矩阵存在。

思路:这个类似前面的第三题,我们知道点\((x,y)\)的前缀和是:\(\sum ^x _{i=1}\sum ^y _{j=1}\sum ^i _{k=1}\sum ^j _{h=1}d[k][h]\)。(\(d[k][h]\)表示差分),这个式子我们可以写成:\(\sum ^x _{i=1}\sum ^y _{j=1}d[i][j]*(x+1-i)*(y+1-j)\)
展开式为:\((x+1)*(y+1)*\sum ^x _{i=1}\sum ^y _{j=1}d[i][j]-(y+1)\sum ^x _{i=1}\sum ^y _{j=1}d[i][j]*i-(x+1)*\sum ^x _{i=1}\sum ^y _{j=1}d[i][j]*j+\sum ^x _{i=1}\sum ^y _{j=1}d[i][j]*i*j\)。所以我们只需要维护:\(d[i][j],d[i][j]*x,d[i][j]*y,d[i][j]*x*y\)即可。

#include <bits/stdc++.h>
#include <queue>
using namespace std;
typedef long long ll;
#define endl '\n'
#define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
#define _for(i, a, b) for (int i=(a); i<=(b); i++)
const int INF = 0x7fffffff;
const int MAXN = 5e5 + 10;

ll n, m, tree1[2050][2050], tree2[2050][2050], tree3[2050][2050], tree4[2050][2050];


inline int lowbit(int x){
	return x & (-x);
}

void updata(int x, int y, int d){
	for(int i = x; i <= n; i += lowbit(i)){
		for(int j = y; j <= m; j += lowbit(j)){
			tree1[i][j] += d;
			tree2[i][j] += x * d;
			tree3[i][j] += y * d;
			tree4[i][j] += x * y * d;
		}
	}
}

ll getsum(int x, int y){
	ll res = 0;
	for(int i = x; i > 0; i -= lowbit(i)){
		for(int j = y; j > 0; j -= lowbit(j)){
			res += (x + 1) * (y +1) * tree1[i][j] - (x + 1) * tree3[i][j] - (y + 1) * tree2[i][j] + tree4[i][j];
		}
	}
	return res;
}

int main(){
	int id, a, b, c, d, x, y, k;
	cin >> n >> m;
	while(scanf("%d", &id) != EOF){
		if(id == 1){
			cin >> a >> b >> c >> d >> k;
			updata(a,b,k);
			updata(a,d+1,-k);
			updata(c+1,b,-k);
			updata(c+1,d+1,k);
		}
		else if(id == 2){
			cin >> a >> b >> c >> d;
			cout << getsum(c,d) - getsum(c,b-1) - getsum(a-1,d) + getsum(a-1,b-1) << endl;
		}
	}
	return 0;
}

如果以上内容有错误的地方,请在下面评论指出,谢谢了!!!

posted @ 2021-05-13 11:53  h星宇  阅读(86)  评论(0编辑  收藏  举报