前言:
线段树的介绍略,可参见《算法进阶》P210~P223
内容详见视频课:
线段树及其应用
模板代码:
1.单点修改,区间查询区间和
#include<bits/stdc++.h>
using namespace std;
const int MAXN=100000+10;
char opt;//类型符,C修改,Q查询
int x,v,l,r;//C指把a[x]改为v,Q指询问区间[l,r]上的区间和
int n,m,a[MAXN];
inline int read(){//快读int
int x=0,f=1;char ch=getchar();
if(!isdigit(ch)){if(ch==45)f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
char get(){//快读char
char ch=getchar();
while(isdigit(ch))ch=getchar();
return ch;
}
struct SegmentTree{
int l,r;//左右端点
int sum;//附加信息,如区间和
}tree[MAXN*4];
void build(int p,int l,int r){
tree[p].l=l;tree[p].r=r;//确定树的左右端点
if(l==r){tree[p].sum=a[l];return ;}//如果左右相等,那么确定附加信息并返回
int mid=(l+r)/2;
build(p*2,l,mid);//递归构造左子树
build(p*2+1,mid+1,r);//递归构造右子树
tree[p].sum=tree[p*2].sum+tree[p*2+1].sum;//向上更新信息,pushup
}
void print(int p){//打印线段树,可用于调试
printf("%d %d %d %d\n",p,tree[p].l,tree[p].r,tree[p].sum);//输出对应信息
if(tree[p].l==tree[p].r)return ;
print(p*2);
print(p*2+1);
}
void update(int p,int x,int v){//单点修改,将a[x]修改为v
if(tree[p].l==tree[p].r) {tree[p].sum=v;return;}//找到目标,直接更新
//折半查找(修改点x不在左半就在右半)
int mid=(tree[p].l+tree[p].r)/2;//求区间中点
if(x<=mid)update(p*2,x,v);//递归更新左半
else update(p*2+1,x,v);//递归更新右半
tree[p].sum=tree[p*2].sum+tree[p*2+1].sum;//pushup
}
int query(int p,int l,int r){//返回区间[l,r]的区间和
//若目标区间完全包含节点p代表的区间,直接返回区间值
if(l<=tree[p].l&&tree[p].r<=r)return tree[p].sum;
int mid=(tree[p].l+tree[p].r)/2;//求区间中点
/*int val=0;
if(l<=mid)val+=query(p*2,l,r);
if(r>mid)val+=query(p*2+1,l,r);
return val;*///版本1
if(r<=mid)return query(p*2,l,r);//完全在左半
else if(l>mid)return query(p*2+1,l,r);//完全在右半
else return query(p*2,l,r)+query(p*2+1,l,r);//横跨中点
//版本2
}
int main(){
n=read(),m=read();
for(int i=1;i<=n;i++)a[i]=read();
build(1,1,n);
//print(1);
for(int i=1;i<=m;i++){
opt=get();
if(opt=='C'){
x=read(),v=read();
update(1,x,v);
}
else{
l=read(),r=read();
printf("%d\n",query(1,l,r));
}
}
return 0;
}
2.单点修改,区间查询最大值
#include<bits/stdc++.h>
using namespace std;
const int MAXN=100000+10;
char opt;//类型符,C修改,Q查询
int x,v,l,r;//C指把a[x]改为v,Q指询问区间[l,r]上的区间最大值
int n,m,a[MAXN];
inline int read(){//快读int
int x=0,f=1;char ch=getchar();
if(!isdigit(ch)){if(ch==45)f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
char get(){//快读char
char ch=getchar();
while(isdigit(ch))ch=getchar();
return ch;
}
struct SegmentTree{
int l,r;//左右端点
int maxn;//附加信息,如区间最大值
}tree[MAXN*4];
void build(int p,int l,int r){
tree[p].l=l;tree[p].r=r;//确定树的左右端点
if(l==r){tree[p].maxn=a[l];return ;}//如果左右相等,那么确定附加信息并返回
int mid=(l+r)/2;
build(p*2,l,mid);//递归构造左子树
build(p*2+1,mid+1,r);//递归构造右子树
tree[p].maxn=max(tree[p*2].maxn,tree[p*2+1].maxn);//向上更新信息,pushup
}
void print(int p){//打印线段树,可用于调试
printf("%d %d %d %d\n",p,tree[p].l,tree[p].r,tree[p].maxn);//输出对应信息
if(tree[p].l==tree[p].r)return ;
print(p*2);
print(p*2+1);
}
void update(int p,int x,int v){//单点修改,将a[x]修改为v
if(tree[p].l==tree[p].r) {tree[p].maxn=v;return;}//找到目标,直接更新
//折半查找(修改点x不在左半就在右半)
int mid=(tree[p].l+tree[p].r)/2;//求区间中点
if(x<=mid)update(p*2,x,v);//递归更新左半
else update(p*2+1,x,v);//递归更新右半
tree[p].maxn=max(tree[p*2].maxn,tree[p*2+1].maxn);//pushup
}
int query(int p,int l,int r){//返回区间[l,r]的区间最大值
//若目标区间完全包含节点p代表的区间,直接返回区间值
if(l<=tree[p].l&&tree[p].r<=r)return tree[p].maxn;
int mid=(tree[p].l+tree[p].r)/2;//求区间中点
if(r<=mid)return query(p*2,l,r);//完全在左半
else if(l>mid)return query(p*2+1,l,r);//完全在右半
else return max(query(p*2,l,r),query(p*2+1,l,r));//横跨中点
}
int main(){
n=read(),m=read();
for(int i=1;i<=n;i++)a[i]=read();
build(1,1,n);
//print(1);
for(int i=1;i<=m;i++){
opt=get();
if(opt=='C'){
x=read(),v=read();
update(1,x,v);
}
else{
l=read(),r=read();
printf("%d\n",query(1,l,r));
}
}
return 0;
}
3.单点修改,区间查询最大连续子段和
#include<bits/stdc++.h>
using namespace std;
const int MAXN=500000+10;
int k;//类型符,2修改,1查询
int x,v,l,r;//C指把a[x]改为v,Q指询问区间[l,r]上的区间最大值
int n,m,a[MAXN];
inline int read(){//快读int
int x=0,f=1;char ch=getchar();
if(!isdigit(ch)){if(ch==45)f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct SegmentTree{
int l,r;//左右端点
int sum;//区间和
int dat;//区间最大连续子段和
int lmax;//紧靠左端的最大连续子段和
int rmax;//紧靠右端的最大连续子段和
}tree[MAXN*4];
void build(int p,int l,int r){
tree[p].l=l;tree[p].r=r;//确定树的左右端点
if(l==r){tree[p].sum=tree[p].dat=tree[p].lmax=tree[p].rmax=a[l];return ;}//如果左右相等,那么确定附加信息并返回
int mid=(l+r)/2;
build(p*2,l,mid);//递归构造左子树
build(p*2+1,mid+1,r);//递归构造右子树
tree[p].sum=tree[p*2].sum+tree[p*2+1].sum;//向上更新信息,pushup
tree[p].lmax=max(tree[p*2].lmax,tree[p*2].sum+tree[p*2+1].lmax);
tree[p].rmax=max(tree[p*2+1].rmax,tree[p*2+1].sum+tree[p*2].rmax);
tree[p].dat=max(tree[p*2].dat,max(tree[p*2+1].dat,tree[p*2].rmax+tree[p*2+1].lmax));
}
void print(int p){//打印线段树,可用于调试
printf("%d %d %d %d %d %d %d\n",p,tree[p].l,tree[p].r,tree[p].sum,tree[p].dat,tree[p].lmax,tree[p].rmax);//输出对应信息
if(tree[p].l==tree[p].r)return ;
print(p*2);
print(p*2+1);
}
void update(int p,int x,int v){//单点修改,将a[x]修改为v
if(tree[p].l==tree[p].r) {tree[p].sum=tree[p].dat=tree[p].lmax=tree[p].rmax=v;return;}//找到目标,直接更新
//折半查找(修改点x不在左半就在右半)
int mid=(tree[p].l+tree[p].r)/2;//求区间中点
if(x<=mid)update(p*2,x,v);//递归更新左半
else update(p*2+1,x,v);//递归更新右半
tree[p].sum=tree[p*2].sum+tree[p*2+1].sum;//向上更新信息,pushup
tree[p].lmax=max(tree[p*2].lmax,tree[p*2].sum+tree[p*2+1].lmax);
tree[p].rmax=max(tree[p*2+1].rmax,tree[p*2+1].sum+tree[p*2].rmax);
tree[p].dat=max(tree[p*2].dat,max(tree[p*2+1].dat,tree[p*2].rmax+tree[p*2+1].lmax));
}
SegmentTree query(int p,int l,int r){//返回区间[l,r]的最大子段和
//若目标区间完全包含节点p代表的区间,直接返回区间值
if(l<=tree[p].l&&tree[p].r<=r)return tree[p];
int mid=(tree[p].l+tree[p].r)/2;//求区间中点
if(r<=mid)return query(p*2,l,r);//完全在左半
else if(l>mid)return query(p*2+1,l,r);//完全在右半
else{//横跨中点
SegmentTree a,b,c;
a=query(p*2,l,r);
b=query(p*2+1,l,r);
c.sum=a.sum+b.sum;
c.lmax=max(a.lmax,a.sum+b.lmax);
c.rmax=max(b.rmax,b.sum+a.rmax);
c.dat=max(a.dat,max(b.dat,a.rmax+b.lmax));
return c;
}
}
int main(){
n=read(),m=read();
for(int i=1;i<=n;i++)a[i]=read();
build(1,1,n);
//print(1);
for(int i=1;i<=m;i++){
k=read();
if(k==2){
x=read(),v=read();
update(1,x,v);
}
else{
l=read(),r=read();
if(l>r)swap(l,r);
printf("%d\n",query(1,l,r).dat);
}
}
return 0;
}
4.区间修改,区间查询区间和(暴力)
#include<bits/stdc++.h>
using namespace std;
const int MAXN=100000+10;
char opt;//类型符,C修改,Q查询
int l,r,d;//C指把a[l]~a[r]都加上d,Q指询问区间[l,r]上的区间和
int n,m,a[MAXN];
inline int read(){//快读int
int x=0,f=1;char ch=getchar();
if(!isdigit(ch)){if(ch==45)f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
char get(){//快读char
char ch=getchar();
while(isdigit(ch))ch=getchar();
return ch;
}
struct SegmentTree{
int l,r;//左右端点
int sum;//附加信息,如区间和
}tree[MAXN*4];
void build(int p,int l,int r){
tree[p].l=l;tree[p].r=r;//确定树的左右端点
if(l==r){tree[p].sum=a[l];return ;}//如果左右相等,那么确定附加信息并返回
int mid=(l+r)/2;
build(p*2,l,mid);//递归构造左子树
build(p*2+1,mid+1,r);//递归构造右子树
tree[p].sum=tree[p*2].sum+tree[p*2+1].sum;//向上更新信息,pushup
}
void print(int p){//打印线段树,可用于调试
printf("%d %d %d %d\n",p,tree[p].l,tree[p].r,tree[p].sum);//输出对应信息
if(tree[p].l==tree[p].r)return ;
print(p*2);
print(p*2+1);
}
void add(int p,int d){
//tree[p].sum+=d*(tree[p].r-tree[p].l+1);
//进入时完成,此时要注释掉pushup
if(tree[p].l==tree[p].r){tree[p].sum+=d/*如果进入时完成,那么前面这句删去*/;return;}
add(p*2,d);
add(p*2+1,d);
tree[p].sum=tree[p*2].sum+tree[p*2+1].sum;//pushup
}
void update(int p,int l,int r,int d){//区间修改,将a[l]~a[r]都加上d
if(tree[p].l==tree[p].r) {add(p,d);return;}//找到目标,直接更新
//折半查找(修改点x不在左半就在右半)
int mid=(tree[p].l+tree[p].r)/2;//求区间中点
if(r<=mid)update(p*2,l,r,d);//递归更新左半
else if(l>mid)update(p*2+1,l,r,d);//递归更新右半
else update(p*2,l,r,d),update(p*2+1,l,r,d);//更新两半
tree[p].sum=tree[p*2].sum+tree[p*2+1].sum;//pushup
}
int query(int p,int l,int r){//返回区间[l,r]的区间和
//若目标区间完全包含节点p代表的区间,直接返回区间值
if(l<=tree[p].l&&tree[p].r<=r)return tree[p].sum;
int mid=(tree[p].l+tree[p].r)/2;//求区间中点
/*int val=0;
if(l<=mid)val+=query(p*2,l,r);
if(r>mid)val+=query(p*2+1,l,r);
return val;*///版本1
if(r<=mid)return query(p*2,l,r);//完全在左半
else if(l>mid)return query(p*2+1,l,r);//完全在右半
else return query(p*2,l,r)+query(p*2+1,l,r);//横跨中点
//版本2
}
int main(){
n=read(),m=read();
for(int i=1;i<=n;i++)a[i]=read();
build(1,1,n);
//print(1);
for(int i=1;i<=m;i++){
opt=get();
if(opt=='C'){
l=read(),r=read(),d=read();
update(1,l,r,d);
}
else{
l=read(),r=read();
printf("%d\n",query(1,l,r));
}
}
return 0;
}
5.区间修改,区间查询区间和(懒标记)
#include<bits/stdc++.h>
using namespace std;
const int MAXN=100000+10;
char opt;//类型符,C修改,Q查询
int l,r,d;//C指把a[l]~a[r]都加上d,Q指询问区间[l,r]上的区间和
int n,m,a[MAXN];
inline int read(){//快读int
int x=0,f=1;char ch=getchar();
if(!isdigit(ch)){if(ch==45)f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
char get(){//快读char
char ch=getchar();
while(isdigit(ch))ch=getchar();
return ch;
}
struct SegmentTree{
int l,r;//左右端点
int sum;//附加信息,如区间和
int add;//懒标记
}tree[MAXN*4];
void build(int p,int l,int r){
tree[p].l=l;tree[p].r=r;//确定树的左右端点
if(l==r){tree[p].sum=a[l];return ;}//如果左右相等,那么确定附加信息并返回
int mid=(l+r)/2;
build(p*2,l,mid);//递归构造左子树
build(p*2+1,mid+1,r);//递归构造右子树
tree[p].sum=tree[p*2].sum+tree[p*2+1].sum;//向上更新信息,pushup
}
void print(int p){//打印线段树,可用于调试
printf("%d %d %d %d %d\n",p,tree[p].l,tree[p].r,tree[p].sum,tree[p].add);//输出对应信息
if(tree[p].l==tree[p].r)return ;
print(p*2);
print(p*2+1);
}
void pushdown(int p){
if(tree[p].add){//节点p有懒标记
tree[p*2].sum+=tree[p].add*(tree[p*2].r-tree[p*2].l+1);//更新左右子树的信息
tree[p*2+1].sum+=tree[p].add*(tree[p*2+1].r-tree[p*2+1].l+1);
//为两个子树增加懒标记
tree[p*2].add+=tree[p].add;
tree[p*2+1].add+=tree[p].add;
tree[p].add=0;//清除懒标记
}
}
void update(int p,int l,int r,int d){//区间修改,将a[l]~a[r]都加上d
if(l<=tree[p].l&&tree[p].r<=r) {
tree[p].sum+=d*(tree[p].r-tree[p].l+1);
tree[p].add+=d;//增加懒标记
return;
}//找到目标,直接更新
//折半查找(修改点x不在左半就在右半)
pushdown(p);//下传懒标记
int mid=(tree[p].l+tree[p].r)/2;//求区间中点
if(l<=mid)update(p*2,l,r,d);//递归更新左半
if(r>mid)update(p*2+1,l,r,d);//递归更新右半
tree[p].sum=tree[p*2].sum+tree[p*2+1].sum;//pushup
}
int query(int p,int l,int r){//返回区间[l,r]的区间和
//若目标区间完全包含节点p代表的区间,直接返回区间值
if(l<=tree[p].l&&tree[p].r<=r)return tree[p].sum;
pushdown(p);//下传懒标记
int mid=(tree[p].l+tree[p].r)/2;//求区间中点
/*int val=0;
if(l<=mid)val+=query(p*2,l,r);
if(r>mid)val+=query(p*2+1,l,r);
return val;*///版本1
if(r<=mid)return query(p*2,l,r);//完全在左半
else if(l>mid)return query(p*2+1,l,r);//完全在右半
else return query(p*2,l,r)+query(p*2+1,l,r);//横跨中点
//版本2
}
int main(){
n=read(),m=read();
for(int i=1;i<=n;i++)a[i]=read();
build(1,1,n);
//print(1);
for(int i=1;i<=m;i++){
opt=get();
if(opt=='C'){
l=read(),r=read(),d=read();
update(1,l,r,d);
}
else{
l=read(),r=read();
printf("%d\n",query(1,l,r));
}
}
return 0;
}
例题精选:
1.P1471
2.CF438D
来源:CF438D
题解:见[题解]CF438D