树状数组的模板题

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

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

这是一道模板题。
给定数列 a 1 , a 2 , … , a n a_1,a_2,\dots,a_n a1,a2,,an,你需要进行m各操作,操作有两类:

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

输入格式

第一行包含 2 2 2个正整数 n , m n,m n,m,表示数列长度和询问个数。保证 1 ≤ n , m ≤ 1 0 6 1\leq n,m\leq 10^6 1n,m106
第二行 n n n个整数 a 1 , a 2 , … , a n a_1,a_2,\dots,a_n a1,a2,,an,表示初始数列。保证 ∣ a i ∣ ≤ 1 0 6 |a_i|\leq 10^6 ai106
接下来 m m m行,每行一个操作,为以下两种之一:

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

保证 1 ≤ l ≤ r ≤ n , ∣ x ∣ ≤ 1 0 6 1\leq l\leq r \leq n,|x|\leq 10^6 1lrn,x106

输出格式

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

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

#include 
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 , … , a n a_1,a_2,\dots,a_n a1,a2,,an,你需要进行m各操作,操作有两类:

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

输入格式

第一行包含 2 2 2个正整数 n , m n,m n,m,表示数列长度和询问个数。保证 1 ≤ n , m ≤ 1 0 6 1\leq n,m\leq 10^6 1n,m106
第二行 n n n个整数 a 1 , a 2 , … , a n a_1,a_2,\dots,a_n a1,a2,,an,表示初始数列。保证 ∣ a i ∣ ≤ 1 0 6 |a_i|\leq 10^6 ai106
接下来 m m m行,每行一个操作,为以下两种之一:

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

保证 1 ≤ l ≤ r ≤ n , ∣ x ∣ ≤ 1 0 6 1\leq l\leq r \leq n,|x|\leq 10^6 1lrn,x106

输出格式

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

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

#include 
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 , … , a n a_1,a_2,\dots,a_n a1,a2,,an,你需要进行m各操作,操作有两类:

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

输入格式

第一行包含 2 2 2个正整数 n , m n,m n,m,表示数列长度和询问个数。保证 1 ≤ n , m ≤ 1 0 6 1\leq n,m\leq 10^6 1n,m106
第二行 n n n个整数 a 1 , a 2 , … , a n a_1,a_2,\dots,a_n a1,a2,,an,表示初始数列。保证 ∣ a i ∣ ≤ 1 0 6 |a_i|\leq 10^6 ai106
接下来 m m m行,每行一个操作,为以下两种之一:

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

保证 1 ≤ l ≤ r ≤ n , ∣ x ∣ ≤ 1 0 6 1\leq l\leq r \leq n,|x|\leq 10^6 1lrn,x106

输出格式

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

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

#include 
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 × m n\times m n×m的零矩阵 A A A,你需要完成如下操作:

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

输入格式
输入的第一行有两个正整数 n , m n,m n,m
接下来若干行,每行一个操作,直到文件结束。
输出格式
对于每个 2 2 2操作,输出一个整数,表示对于这个操作的回答。
对于全部数据, 1 ≤ n , m ≤ 2 12 , 1 ≤ x , a , c ≤ n , 1 ≤ y , b , d ≤ m , ∣ k ∣ ≤ 1 0 5 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 1n,m212,1x,a,cn,1y,b,dm,k105,保证操作数目不超过 3 × 1 0 5 3 \times 10^5 3×105,且询问的子矩阵存在。

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

#include 
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 × m n\times m n×m的零矩阵 A A A,你需要完成如下操作:

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

输入格式
输入的第一行有两个正整数 n , m n,m n,m
接下来若干行,每行一个操作,直到文件结束。
输出格式
对于每个 2 2 2操作,输出一个整数,表示对于这个操作的回答。
对于全部数据, 1 ≤ n , m ≤ 2 12 , 1 ≤ x , a , c ≤ n , 1 ≤ y , b , d ≤ m , ∣ k ∣ ≤ 1 0 5 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 1n,m212,1x,a,cn,1y,b,dm,k105,保证操作数目不超过 3 × 1 0 5 3 \times 10^5 3×105,且询问的子矩阵存在。

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

#include 
#include 
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 × m n\times m n×m的零矩阵 A A A,你需要完成如下操作:

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

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

思路:这个类似前面的第三题,我们知道点 ( x , y ) (x,y) (x,y)的前缀和是: ∑ i = 1 x ∑ j = 1 y ∑ k = 1 i ∑ h = 1 j d [ k ] [ h ] \sum ^x _{i=1}\sum ^y _{j=1}\sum ^i _{k=1}\sum ^j _{h=1}d[k][h] i=1xj=1yk=1ih=1jd[k][h]。( d [ k ] [ h ] d[k][h] d[k][h]表示差分),这个式子我们可以写成: ∑ i = 1 x ∑ j = 1 y d [ i ] [ j ] ∗ ( x + 1 − i ) ∗ ( y + 1 − j ) \sum ^x _{i=1}\sum ^y _{j=1}d[i][j]*(x+1-i)*(y+1-j) i=1xj=1yd[i][j](x+1i)(y+1j)
展开式为: ( x + 1 ) ∗ ( y + 1 ) ∗ ∑ i = 1 x ∑ j = 1 y d [ i ] [ j ] − ( y + 1 ) ∑ i = 1 x ∑ j = 1 y d [ i ] [ j ] ∗ i − ( x + 1 ) ∗ ∑ i = 1 x ∑ j = 1 y d [ i ] [ j ] ∗ j + ∑ i = 1 x ∑ j = 1 y d [ i ] [ j ] ∗ i ∗ 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 (x+1)(y+1)i=1xj=1yd[i][j](y+1)i=1xj=1yd[i][j]i(x+1)i=1xj=1yd[i][j]j+i=1xj=1yd[i][j]ij。所以我们只需要维护: d [ i ] [ j ] , d [ i ] [ j ] ∗ x , d [ i ] [ j ] ∗ y , d [ i ] [ j ] ∗ x ∗ y d[i][j],d[i][j]*x,d[i][j]*y,d[i][j]*x*y d[i][j],d[i][j]x,d[i][j]y,d[i][j]xy即可。

#include 
#include 
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;
}

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

你可能感兴趣的:(例题,c++,算法,开发语言)