区间修改
线段树除了点修改之外还有更多的作用
Add(l,r,v):把a[l],a[l+1],a[l+2]…a[r]中所有元素加v
Query(l,r):计算子序列a[l],a[l+1],…,a[r]的元素和,最小值和最大值
当然如果你想让你的代码拥有更多的黑科技气息(附带运算速度加成),你可以采用 位运算 这种优(zhuang)秀(bi)的技巧
定义线段树需要维护的区间和,最小值和最大值分别为sum,min,max.
信息维护的代码如下,假设修改/查询范围均为[y1,y2]
void maintain(int o,int l,int r){
int lc=o*2,rc=o*2+1;
sum[o]=minv[o]=maxv[o]=0; //这一语句不能删,它是用来淸零叶节点的
if(r>l){ // 维护左右子树
sum[o]=sum[lc]+sum[rc];
minv[o]=min(minv[lc],minv[rc]);
maxv[o]=max(maxv[lc],maxx[rc]);
}
minv[o]+=addv[o]; maxv[o]+=addv[o];
sum[o]+=addv[o]*(r-l+1));
}
下面是执行add操作时,将访问到的结点进行信息维护
void update(int o,int l,int r){
int lc=o*2,rc=o*2+1;
if(y1<=l && y2>=r) addv[o]+=v;
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);
}
查询操作就是将查询操作递归分解为若干个不相交子区间,把各个子区间的查询结果加以合并.
void query(int o,int l,int r,int add){ //add表示当前区间的所有祖先结点add值的和
if(y1<=l && y2>=r) {
sumv+=sum[o]+add*(r-l+1);
min=minx(min,minv[o]+add);
max=maxx(max,maxx[o]+add);
}
else {
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): 把a[l],a[l+1],…,a[r]的值全部修改为v
Query(l,r,v): 计算字段和a[l],a[l+1],…,a[r]的元素和,最小值和最大值
set操作也可以类似于add操作进行分解,但是add操作先后顺序可以交换,而set操作却有着严格的先后顺序.比如执行add(1,4,1)在执行add(2,3,2)等价于add(2,3,2)后再执行add(1,4,1),但set不一样.所以set还要修改以前分解好的.
这样以下为修改代码
void pushdown(int o){ //pushdown函数作用是将set值往下传递
int lc=o*2,rc=o*2+1;
if(setv[o]>=0) {
setv[lc]=set[rc]=setv[o];
setv[o]=-1;
}
}
void update(int o,int ql,int qr){
int lc=o*2,rc=o*2+1;
if(y1<=l && r>=y2) setv[o]=v;
else {
pushdown(o);
int m=l+(r-l)/2;
if(y1<=m) update(lc,ql,m);
if(y2>m) update(rc,m+1,qr);
}
maintain(o,l,r);
}
以上就是线段树的一些区间的简单操作(贼爽,速度快得一比).
但线段树复(zhuang)杂(bi)的操作远远不止这些.