线段树(如果不明白线段树请自行百度)
在线段树的基础上只留下左子树,然后进行一些列操作就是树状数组
1. 单点修改 + 区间查询
函数模板:
int bit[maxn],n,m; //n:总的区间长度 m:m次操作
void update(int i,int x){ //给位置i增加x
while(i<=n){
bit[i]+=x;
i+=i&-i; //i&-i得到i的二进制最后一个1
}
}
int sum(int i){ //求位置i的前缀和
int s=0;
while(i>0){
s+=bit[i];
i-=i&-i;
}
return s;
}
int range_sum(int l,int r){ //区间求和
return sum(r)-sum(l-1);
}
如下是代码模板,只需要根据题意修改‘...’的内容即可
#include<bits/stdc++.h>
using namespace std;const int maxn=1e5+10;
const int INF = 0x3f3f3f3f;int bit[maxn],n,m; //n:总的区间长度 m:m次操作
void update(int i,int x){ //给位置i增加x
while(i<=n){
bit[i]+=x;
i+=i&-i; //i&-i得到i的二进制最后一个1
}
}int sum(int i){ //求位置i的前缀和
int s=0;
while(i>0){
s+=bit[i];
i-=i&-i;
}
return s;
}int range_sum(int l,int r){ //区间求和
return sum(r)-sum(l-1);
}int main(){
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&bit[i]);
update(i,bit[i]);
}
while(m--){
...;
}
return 0;
}
2. 区间修改 + 单点查询
通过“差分”(就是记录数组中每个元素与前一个元素的差),可以把这个问题转化为问题1。
查询
设原数组为a[i], 设数组bit[i]=a[i]-a[i-1](a[0]=0),则 a[i]=bit[1]+...+bit[i],可以通过求bit[i]的前缀和查询。
修改
当给区间[l,r]加上x的时候,a[l]与前一个元素 a[l-1]的差增加了x,a[r+1]与a[r]的差减少了x。
根据d[i]数组的定义,只需给a[l]加上x,给a[r+1]减去x即可。
函数模板
int a[maxn],bit[maxn],n,m; //n:总的区间长度 m:m次操作
void add(int i,int x){ //给位置i增加x
while(i<=n){
bit[i]+=x;
i+=i&-i; //i&-i得到i的二进制最后一个1
}
}
void range_add(int l,int r,int x){//给区间[l, r]加上x
add(l,x),add(r+1,-x);
}
int ask(int i){ //单点查询
int res=0;
while(i>0){
res+=bit[i];
i-=i&-i;
}
return res;
}
如下是代码模板,只需要根据题意修改‘...’的内容即可
#include<bits/stdc++.h>
using namespace std;const int maxn=1e5+10;
const int INF = 0x3f3f3f3f;int a[maxn],bit[maxn],n,m; //n:总的区间长度 m:m次操作
void add(int i,int x){ //给位置i增加x
while(i<=n){
bit[i]+=x;
i+=i&-i; //i&-i得到i的二进制最后一个1
}
}void range_add(int l,int r,int x){//给区间[l, r]加上x
add(l,x),add(r+1,-x);
}int ask(int i){ //单点查询
int res=0;
while(i>0){
res+=bit[i];
i-=i&-i;
}
return res;
}int main(){
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
add(i,a[i]-a[i-1]);
}
while(m--){
...;
}
return 0;
}
3. 区间修改 + 区间查询
在区间修改的基础上:
位置p的前缀和:
a[1]+...+a[p]=(d[1])+(d[1]+d[2])+...+(d[1]+...+d[p])
d[1]被用了p次,d[2]被用了p-1次…
我们可以写出:位置p的前缀和 =
d[1]*(p-1+1)+...+d[i]*(p-i+1)+...+d[p]*(p-p+1)
==>(p+1)*(d[1]+...+d[p])-{d[1]*1+...+d[p]*p};
那么我们可以维护两个数组的前缀和:
一个数组是 sum1[i]=d[i] 另一个数组是 sum2[i]=d[i]*i。
查询
位置p的前缀和即: (p + 1) * sum1数组中p的前缀和 - sum2数组中p的前缀和。
区间[l, r]的和即:位置r的前缀和 - 位置l的前缀和。
修改
对于sum1数组的修改同问题2中对d数组的修改。
对于sum2数组的修改也类似,我们给 sum2[l] 加上 l * x,给 sum2[r + 1] 减去 (r + 1) * x。
函数模板:
void add(ll i,ll x){ //给位置i增加x
while(i<=n){
sum1[i]+=x;
sum2[i]+=x*i;
i+=i&-i; //i&-i得到i的二进制最后一个1
}
}
void range_add(ll l,ll r,ll x){//给区间[l, r]加上x
add(l,x),add(r+1,-x);
}
ll ask(ll i){ //求位置i的前缀和
ll s=0;
while(i>0){
s+=(i+1)*sum1[i]-sum2[i];
i-=i&-i;
}
return s;
}
ll range_ask(ll l,ll r){ //区间求和
return ask(r)-ask(l-1);
}
如下是代码模板,只需要根据题意修改‘...’的内容即可
#include<bits/stdc++.h>
using namespace std;typedef long long ll;
const int maxn=1e5+10;
const int INF = 0x3f3f3f3f;ll n,m;
ll a[maxn];
ll sum1[maxn],sum2[maxn];void add(ll i,ll x){ //给位置i增加x
while(i<=n){
sum1[i]+=x;
sum2[i]+=x*i;
i+=i&-i; //i&-i得到i的二进制最后一个1
}
}void range_add(ll l,ll r,ll x){//给区间[l, r]加上x
add(l,x),add(r+1,-x);
}ll ask(ll i){ //求位置i的前缀和
ll s=0;
while(i>0){
s+=(i+1)*sum1[i]-sum2[i];
i-=i&-i;
}
return s;
}ll range_ask(ll l,ll r){ //区间求和
return ask(r)-ask(l-1);
}int main(){
scanf("%lld %lld",&n,&m);
for(ll i=1;i<=n;i++){
scanf("%lld",&a[i]);
add(i,a[i]-a[i-1]);
}
while(m--){
...;
}
return 0;
}
用这个做区间修改区间求和的题,无论是时间上还是空间上都比带lazy标记的线段树要优。