【如果你不知道什么是树状数组:请点这里!!!
#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 1≤n,m≤106。
第二行 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 ∣ai∣≤106。
接下来 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 1≤l≤r≤n,∣x∣≤106。
输出格式
对于每个 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 al,al+1,…ar分别加上 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 1≤n,m≤106。
第二行 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 ∣ai∣≤106。
接下来 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 1≤l≤r≤n,∣x∣≤106。
输出格式
对于每个 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 al,al+1,…ar分别加上 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 1≤n,m≤106。
第二行 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 ∣ai∣≤106。
接下来 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 1≤l≤r≤n,∣x∣≤106。
输出格式
对于每个 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]∗(y−i+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 1≤n,m≤212,1≤x,a,c≤n,1≤y,b,d≤m,∣k∣≤105,保证操作数目不超过 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 1≤n,m≤212,1≤x,a,c≤n,1≤y,b,d≤m,∣k∣≤105,保证操作数目不超过 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 1≤n,m≤2048,∣k∣≤500,保证操作数目不超过 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=1x∑j=1y∑k=1i∑h=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=1x∑j=1yd[i][j]∗(x+1−i)∗(y+1−j)。
展开式为: ( 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=1x∑j=1yd[i][j]−(y+1)∑i=1x∑j=1yd[i][j]∗i−(x+1)∗∑i=1x∑j=1yd[i][j]∗j+∑i=1x∑j=1yd[i][j]∗i∗j。所以我们只需要维护: 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]∗x∗y即可。
#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;
}
如果以上内容有错误的地方,请在下面评论指出,谢谢了!!!