树状数组的模板题
【如果你不知道什么是树状数组:请点这里!!!
这是一道模板题。
给定数列\(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;
}
这是一道模板题。
给定数列\(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;
}
这是一道模板题。
给定数列\(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;
}
给定一个\(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;
}
给出一个\(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;
}
给出一个\(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;
}
如果以上内容有错误的地方,请在下面评论指出,谢谢了!!!