我以前写过线段树可以支持点修改与区间查询,其实线段树还可以做的更多,但是也更巧妙,更不好理解:
现在我们写个线段数要支持2个功能:
add(L,R,v):把A(L),A(L+1),.......,A(R)的值全部增加v。
query(L,R):计算子序列A(L),..........,A(R)的元素和,最小值和最大值。
其实我们也可以用点修改做,但是想想如果这样做了时间上还有优势了吗?没有了,所以我们要换种方法我们将add当做信息存储在区间节点上,比如对一个1到n的区间,我将区间(1,n/2)的每一个元素的增加v。我们可以将这个信息adds存储在(1,n/2)区间对应的节点上,对应的(1,n/2)区间对应的和信息,最大值,最小值信息都要进行改变。当进行query操作是,要将经过的节点的adds值累加起来得到addv,当遇到全部覆盖的区间时,将区间对应的和,最大值,最小值与我们addv结合求出正确的解。
可能我说的不太明白,请看下面的代码,应该能加深理解:
//维护节点o,它对应区间[L,R]
int maintain(int o,int L,int R){
int lc=o*2,rc=o*2+1;
sumv[o]=minv[o]=maxv[o]=0;
if (R>L){//考虑左右子树
sumv[o]=sumv[lc]+sumv[rc];
minv[o]=min(minv[lc],minv[rc]);
maxv[o]=max(maxv[lc],maxv[rc]);
}
minv[o]+=addv[o];
maxv[o]+=addv[o];
sumv[o]+=addv[o]*(R-L+1);
//考虑add操作
return 0;
}
//修改区间(y1,y2);
int update(int o,int L,int R){
int lc=o*2,rc=o*2+1;
if (y1<=L&&y2>=R){ //递归边界
add[o]+=v; //累加边界的add值
}else {
int M=L+(R-L)/2;
if (y1<=M) update(lc,L,M);
if (y2>M) update(rc,M+1,R);
}
maintain(o,L,R); //递归结束前重新计算本节点的附加信息
return 0;
}
int _min,_max,_sum;
int query(int o,int L,int R,int add){
if (y1<=L&&y2>=R){
_sum+=sumv[o]+add*(R-L+1);
_min=min(_min,minv[o]+add);
_max=max(_max,maxv[o]+add);
}
else {//递归统计,累加参数add
int M=L+(R-L)/2;
if (y1<=M) query(o*2,L,M,add+addv[o]);
if (y2>M) query(o*2+1,M+1,R,add+addv[o]);
}
}
上面三个函数的功能分别是维护区间,修改区间,和区间查询;
其实这只是基础的应用,还有更深入的应用,我们想想如果我们再加一个操作,set(L,R,v)将区间内的所有的元素都变为v,我们应该如何写这个线段树呢?
通过分析我们可以得到,set操作与add操作如果先后顺序不同,造成的结果也是完全不同的,所有我们新增加了一个函数pushdown,将set与add的信息下移。
int pushdown(int o,int L,int R,int q){
if (sets[o][q]>=0){ //sets[o][q] 为负的话 表示没有信息
sets[o*2][q]=sets[o*2+1][q]=sets[o][q];
sets[o][q]=-1;adds[o][q]=0;
adds[o*2][q]=adds[o*2+1][q]=0;
}
else {
if (sets[o*2][q]>=0) sets[o*2][q]+=adds[o][q];
else adds[o*2][q]+=adds[o][q];
if (sets[o*2+1][q]>=0) sets[o*2+1][q]+=adds[o][q];
else adds[o*2+1][q]+=adds[o][q];
adds[o][q]=0;
}
return 0;
}
update变为:
int update(int o,int L,int R,int q){
if (x1<=L&&x2>=R){
if (sets[o][q]>=0){
sets[o][q]+=v;
}
else adds[o][q]+=v;
}
else {
int M=L+(R-L)/2;
pushdown(o,L,R,q);
if (x1<=M) update(o*2,L,M,q);
else maintain(o*2,L,M,q);
if (x2>M) update(o*2+1,M+1,R,q);
else maintain(o*2+1,M+1,R,q);
}
maintain(o,L,R,q);
return 0;
}
新增加一个setv操作:
int setv(int o,int L,int R,int q){
if (x1<=L&&x2>=R){
sets[o][q]=v;
adds[o][q]=0;
}
else {
int M=L+(R-L)/2;
pushdown(o,L,R,q);
if (x1<=M) setv(o*2,L,M,q);
else maintain(o*2,L,M,q);
if (x2>M) setv(o*2+1,M+1,R,q);
else maintain(o*2+1,M+1,R,q);
}
maintain(o,L,R,q);
return 0;
}
query操作变为如下
int query(int o,int L,int R,int q,int yyy){
if (sets[o][q]>=0){
_sum+=(sets[o][q]+yyy)*(min(R,x2)-max(L,x1)+1);
if (_max<maxv[o][q]+yyy) _max=maxv[o][q]+yyy;
if (_min>minv[o][q]+yyy) _min=minv[o][q]+yyy;
}
else if (x1<=L&&x2>=R){
_sum+=sum[o][q]+yyy*(R-L+1);
if (_max<maxv[o][q]+yyy) _max=maxv[o][q]+yyy;
if (_min>minv[o][q]+yyy) _min=minv[o][q]+yyy;
}
else {
int M=L+(R-L)/2;
if (x1<=M)query(o*2,L,M,q,yyy+adds[o][q]);
if (x2>M)query(o*2+1,M+1,R,q,yyy+adds[o][q]);
}
return 0;
}
这几天在总结线段树,我对线段树做了以下几个分类:
第一类 单点修改单个信息——>单点修改多个信息——>单点修改多个信息(信息之间有关系)并且设计到区间合并
第二类 区间修改单个信息如add操作set操作——>区间修改多个信息并且不同信息之间有联系
第三类 就是一看就是应该是线段树但是不知道给如何建立这棵树(|||-_-)像是我之前做的 FZU 2105。
来道有挑战性的题吧 uva 11992 我今天的例子就是以它为模板的
我的代码:
#include <cstdio>
#include <algorithm>
const int maxn = 500010;
using namespace std;
int sets[maxn*2][23],adds[maxn*2][23],sum[maxn*2][23],minv[maxn*2][23],maxv[maxn*2][23],_sum,_max,_min;
int x1,x2,v;
int maintain(int o,int L,int R,int q){
if (sets[o][q]>=0){
if (R>=L){
sum[o][q]=sets[o][q]*(R-L+1);
maxv[o][q]=sets[o][q];
minv[o][q]=sets[o][q];
adds[o][q]=0;
}
}else {
sum[o][q]=minv[o][q]=maxv[o][q]=0;
if (R>L){
sum[o][q]=sum[o*2][q]+sum[o*2+1][q];
if (minv[o*2][q]>minv[o*2+1][q]) minv[o][q]=minv[o*2+1][q];
else minv[o][q]=minv[o*2][q];
if (maxv[o*2][q]<maxv[o*2+1][q]) maxv[o][q]=maxv[o*2+1][q];
else maxv[o][q]=maxv[o*2][q];
}
minv[o][q]+=adds[o][q];maxv[o][q]+=adds[o][q];sum[o][q]+=adds[o][q]*(R-L+1);
}
return 0;
}
int pushdown(int o,int L,int R,int q){
if (sets[o][q]>=0){
sets[o*2][q]=sets[o*2+1][q]=sets[o][q];
sets[o][q]=-1;adds[o][q]=0;
adds[o*2][q]=adds[o*2+1][q]=0;
}
else {
if (sets[o*2][q]>=0) sets[o*2][q]+=adds[o][q];
else adds[o*2][q]+=adds[o][q];
if (sets[o*2+1][q]>=0) sets[o*2+1][q]+=adds[o][q];
else adds[o*2+1][q]+=adds[o][q];
adds[o][q]=0;
}
return 0;
}
int update(int o,int L,int R,int q){
if (x1<=L&&x2>=R){
if (sets[o][q]>=0){
sets[o][q]+=v;
}
else adds[o][q]+=v;
}
else {
int M=L+(R-L)/2;
pushdown(o,L,R,q);
if (x1<=M) update(o*2,L,M,q);
else maintain(o*2,L,M,q);
if (x2>M) update(o*2+1,M+1,R,q);
else maintain(o*2+1,M+1,R,q);
}
maintain(o,L,R,q);
return 0;
}
int setv(int o,int L,int R,int q){
if (x1<=L&&x2>=R){
sets[o][q]=v;
adds[o][q]=0;
}
else {
int M=L+(R-L)/2;
pushdown(o,L,R,q);
if (x1<=M) setv(o*2,L,M,q);
else maintain(o*2,L,M,q);
if (x2>M) setv(o*2+1,M+1,R,q);
else maintain(o*2+1,M+1,R,q);
}
maintain(o,L,R,q);
return 0;
}
int query(int o,int L,int R,int q,int yyy){
if (sets[o][q]>=0){
_sum+=(sets[o][q]+yyy)*(min(R,x2)-max(L,x1)+1);
if (_max<maxv[o][q]+yyy) _max=maxv[o][q]+yyy;
if (_min>minv[o][q]+yyy) _min=minv[o][q]+yyy;
}
else if (x1<=L&&x2>=R){
_sum+=sum[o][q]+yyy*(R-L+1);
if (_max<maxv[o][q]+yyy) _max=maxv[o][q]+yyy;
if (_min>minv[o][q]+yyy) _min=minv[o][q]+yyy;
}
else {
int M=L+(R-L)/2;
if (x1<=M)query(o*2,L,M,q,yyy+adds[o][q]);
if (x2>M)query(o*2+1,M+1,R,q,yyy+adds[o][q]);
}
return 0;
}
int main (){
int r,c,m;
while (scanf("%d%d%d",&r,&c,&m)!=EOF){
for (int i=0;i<=c*2;i++)
for (int j=0;j<=r;j++){
adds[i][j]=sum[i][j]=0;
sets[i][j]=-1;
maxv[i][j]=0;
minv[i][j]=0;
}
for (int i=0;i<m;i++){
int y1,y2,q;scanf("%d",&q);
if (q==1){
scanf("%d%d%d%d%d",&y1,&x1,&y2,&x2,&v);
for (int j=y1;j<=y2;j++) update(1,1,c,j);
}
if (q==2){
scanf("%d%d%d%d%d",&y1,&x1,&y2,&x2,&v);
for (int j=y1;j<=y2;j++) setv(1,1,c,j);
}
if (q==3){
scanf("%d%d%d%d",&y1,&x1,&y2,&x2);
_sum=0;_max=-1000000000;_min=1000000000;
for (int j=y1;j<=y2;j++) query(1,1,c,j,0);
printf("%d %d %d\n",_sum,_min,_max);
}
}
}
return 0;
}