序言
今天老师教了树状数组,并要求我们做题所以水了水题,要学树状数组的,点这——>树状数组,然后老师在下午时,又叫我们用线段树写 求和光明OJ的题,所以我来补代码了
First Problem求和
时间限制: 1000 ms 空间限制: 131072 KB
题目描述
输入一个数列A1,A2….An(1<=N<=100000),在数列上进行M(1<=M<=100000)次操作,操作有以下两种:
- 格式为 C C C I I I X X X,其中 C C C为字符“ C C C”, I I I和 X X X ( 1 ≤ I ≤ N , X ≤ 10000 ) (1\le I \le N,X\le 10000) (1≤I≤N,X≤10000)都是整数,表示把把 a I a_I aI改为 X X X
- 格式为 Q Q Q L L L R R R,其中 Q Q Q为字符“ Q Q Q”, L L L和 R R R表示询问区间为 [ L , R ] [L,R] [L,R] ( 1 ≤ L < = R ≤ N ) (1\le L<=R\le N) (1≤L<=R≤N),表示询问 A L + … + A R A_L+…+A_R AL+…+AR的值。
输入
第一行输入 N ( 1 ≤ N ≤ 100000 ) N(1\le N\le 100000) N(1≤N≤100000),表述数列的长度,接下来 N N N行,每行一个整数(绝对值不超过 10000 10000 10000)依次输入每个数;接下来输入一个整数 M ( 1 ≤ M ≤ 100000 ) M(1\le M\le 100000) M(1≤M≤100000),表示操作数量,接下来 M M M行,每行为 C C C I I I X X X或者 Q Q Q L L L R R R。
输出
对于每个 Q Q Q L L L R R R 的操作输出答案。
样例输入
5
1
2
3
4
5
3
Q 2 3
C 3 9
Q 1 4
样例输出
5
16
思路
这题是模板题呀,我们只需注意是将
a
i
a_i
ai改为
x
x
x,而不是加
x
x
x,我当时脑子没转过来
所以
a
d
d
add
add就应该是
a
d
d
(
i
,
x
−
l
[
i
]
)
add(i,x-l[i])
add(i,x−l[i]),
l
[
i
]
l[i]
l[i]是原来的值,最后将
l
[
j
]
=
x
l[j]=x
l[j]=x就行了。询问,大家都会吧,就不水了
AC Code(树状数组)
#include<bits/stdc++.h>
using namespace std;
int x,y,ans,n,m,b[1000000];
char ph;
int a[1000000];
int max(int x,int y){
return x>y?x:y;
}
void make(int l,int r,int i){
if(l==r){
a[i]=b[l];
return ;
}
int m=(l+r)/2;
make(l,m,i<<1);
make(m+1,r,i<<1|1);
a[i]=a[i<<1]+a[i<<1|1];
}
void qry(int i,int l,int r){
if(r<x||l>y) return ;
if(l>=x&&r<=y){
ans+=a[i];
return;
}
int m=(l+r)>>1;
qry(i<<1,l,m);
qry(i<<1|1,m+1,r);
}
void add(int i,int l,int r){
if(l==r){a[i]=y;return ;}
int m=(l+r)>>1;
if(x<=m) add(i<<1,l,m);
else add(i<<1|1,m+1,r);
a[i]=a[i<<1]+a[i<<1|1];
}
int main(){
ios::sync_with_stdio(false);
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&b[i]);
make(1,n,1);
scanf("%d",&m);
for(int i=1;i<=m;i++){
ans=0;
scanf("%s%d%d",&ph,&x,&y);
if(ph=='Q'){
qry(1,1,n);
printf("%d\n",ans);
}
else add(1,1,n);
}
return 0;
}
Second problem 【模板】树状数组 1
题目描述
如题,已知一个数列,你需要进行下面两种操作:
-
将某一个数加上 x x x
-
求出某区间每一个数的和
输入格式
第一行包含两个正整数 n , m n,m n,m,分别表示该数列数字的个数和操作的总个数。
第二行包含 n n n 个用空格分隔的整数,其中第 i i i 个数字表示数列第 i i i 项的初始值。
接下来 m m m 行每行包含 3 3 3 个整数,表示一个操作,具体如下:
-
1 x k
含义:将第 x x x 个数加上 k k k -
2 x y
含义:输出区间 [ x , y ] [x,y] [x,y] 内每个数的和
输出格式
输出包含若干行整数,即为所有操作 2 2 2 的结果。
样例
样例输入1
5 5
1 5 4 2 3
1 1 3
2 2 5
1 3 -1
1 4 2
2 1 4
样例输出1
14
16
提示
数据范围:
对于
30
%
30\%
30% 的数据,
1
≤
n
≤
8
1 \le n \le 8
1≤n≤8,
1
≤
m
≤
10
1\le m \le 10
1≤m≤10;
对于
70
%
70\%
70% 的数据,
1
≤
n
,
m
≤
1
0
4
1\le n,m \le 10^4
1≤n,m≤104;
对于
100
%
100\%
100% 的数据,
1
≤
n
,
m
≤
5
×
1
0
5
1\le n,m \le 5\times 10^5
1≤n,m≤5×105。
样例说明:
故输出结果14、16
思路
这道题比上面那道还模板,给第
x
x
x个数加上
k
k
k直接
a
d
d
(
x
,
k
)
add(x,k)
add(x,k)就行,十分水。
求和就简单讲一讲,算
1
−
n
1-n
1−n大家都会吧,就
int q(int x){//表示1~x的和
int ans=0;//统计和
while(x!=0){//表示不出范围也可以写成x>0
ans+=a[x];//a[x]表示树状数组下标为[x]的数
x-=lowbit(x);//对于每个数的lowbit为这个数二进制的最后1个'1'
}
return ans;//返回前缀和
}
有了前缀和就可以求
a
l
,
r
a_{l,r}
al,r的总值,
a
l
,
r
=
q
(
r
)
−
q
(
l
−
1
)
a_{l,r}=q(r)-q(l-1)
al,r=q(r)−q(l−1)如果记不得是
r
−
(
l
−
1
)
r-(l-1)
r−(l−1)还是
(
l
−
1
)
−
r
(l-1)-r
(l−1)−r
可以写成
a
i
,
j
=
a
b
s
(
q
(
l
−
1
)
−
q
(
r
)
)
a_{i,j}=abs(q(l-1)-q(r))
ai,j=abs(q(l−1)−q(r))取个绝对值
AC Code
#include<bits/stdc++.h>
using namespace std;
int a[500001],n,m,l;
int lowbit(int x){
return x&(-x);
}
int q(int x){
int ans=0;
while(x!=0){
ans+=a[x];
x-=lowbit(x);
}
return ans;
}
void add(int x,int k){
while(x<=n){
a[x]+=k;
x+=lowbit(x);
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
cin>>l;
add(i,l);
}
for(int i=1;i<=m;i++){
int r;
int j,k;
scanf("%d%d%d",&r,&j,&k);
if(r==2){
int sum=q(k)-q(j-1);
printf("%d\n",sum);
}else{
add(j,k);
}
}
return 0;
}