昨天复习了几个月前自己写下的线段树区间操作总结的算法笔记,虽然注释很多,但重新再看时发现注释仍然不够。于是这次重新复习了线段树区间操作算法,已一个初学者的角度来加上许多注释和笔记。我将线段树的区间操作,包括区间运算,区间求值封装到了一个类里。两种操作又包含普通版和lazy标签版,下面是代码:
#include
#include
#include
using namespace std;
const int maxn=10000;
/*
算法中出现的左和右变量有很多不同的含义,需要区分清楚:
一是问题给出的数组,(待求数组)
二是保二叉树的数组,(存储数组) 待求数组在这个数组的最后,相当于子叶,前面是父节点
三是对存储数组的抽象。(满二叉树) 根节点编号为1,对应上面两个数组的0
以根节点为例,节点编号使用二叉树编号0,其覆盖的范围用存储数组编号,覆盖全部,所以是0~(子叶数量)
*/
class Tree {
int first; //第一片叶在数组中的下标为 first,在树中为0。1是根节点在数组中的位置
int leaf; //有效叶的数量,最后一层中的有效数值
int sum; //node数组的大小
int *node; //保存树的数组,保存子节点和
int *add; //lazy标签数组,
int rank; //树的深度
int tl,tr,tn; //将操作范围和操作数当做全局变量,因为递归操作不需要他们做参数
/*
区间运算操作1,不使用lazy标签,开始调用的参数是(1, 0,first-1)1是当前节点在树总的节点编号,0和first是当前节点存储数组中管理的范围
过程:从根节点开始,通过判断左右子叶的覆盖范围是否完全在需要求和的范围,直至到了叶节点,当回溯时,全部需要操作的叶节点都已经逐个进
行了操作,回溯就是来时经过的全部父节点都更新一下。
*/
//又add()调用,对tl到tr范围内的树叶进行加减tn运算,并更新上路
void fun1(int o,int l,int r) { //o:节点数组下标 l,r:该节点覆盖的树叶在存储数组的下标
if(rtr) return; //这个节点的覆盖范围包含了不需要操作的节点,这时直接跳过
if(l==r) {
node[o]+=tn;
return;
}
int m=(l+r)/2;
fun1(o*2,l,m);
fun1(o*2+1,m+1,r);
node[o] = node[o*2]+node[o*2+1];
}
/*
更新节点lazy标签又add2()调用,使用lazy操作的二叉树符合下面的条件:
1,有lazy标签的节点的实际值就是实际值,上面全部父节点的当前值也是实际值
2,lazy标签的节点覆盖的全部节点的实际值都是当前值加上lazy操作
3,lazy标签下面的全部子节点都不会出现lazy标签
4,包含lazy标签的节点,其左右子树都是对称的
*/
//更新节点操作,又fun2()调用,将o节点的lazy标签转移到两个子节点,
void update(int o,int l,int r){
int len = (r-l+1)/2; //子节点覆盖范围的长度
node[o*2] += len*add[o];
node[o*2+1] += len*add[o];
add[o*2] = add[o*2+1] = add[o];
add[o]=0;
}
/*
区间运算操作2:使用lazy标签。与区间求和操作1相似,都是从更节点开始向下递归操作,但这时若遇到当前节点覆盖的范围
完全在需要操作的范围的情况就可以为其加上lazy标签,从而代替更新它全部子节点的值的操作。需要注意的是如果当前节点
不符合上面条件需要继续进入子节点时,需要先消除它的lazy标签,也就是将lazy标签向下移一层,然后在递归。
*/
//又add2()调用,以lazy思想实验线段树区间运算操作
void fun2(int o,int l,int r){
if(tl<=l && r<=tr){
add[o] += tn;
node[o] += (r-l+1)*tn;
}else{
int mid = (r+l)/2;
if(add[o]) update(o,l,r); //下移lazy标签
if(tl<=mid) fun2(2*o,l,mid);
if(tr>=mid+1) fun2(2*o+1,mid+1,r);
node[o]=node[2*o]+node[2*o+1];
}
}
/*
区间求和操作1:从下往上版。主要操作对象是二叉树数组中需要操作的范围两边代表的左子叶和右子叶。需要了解一个规律,
若左边界是右子叶或右边界是左子叶,那么父亲就包含了范围外的值,需要转跳节点。另外,结束的条件是左右边界相邻。
这是有两种情况,父亲相同或不相同,需要区分对待操作。
*/
//求叶子s到e的数值和(初级区间求和)
int ask_sum(int s,int e) {
s+=first , e+=first;
int ans = 0;
while(e-s>1) { //若相邻,则再往上必定有交集
if(e%2==0) { //上一级范包含了其他范围
ans+=node[e];
e=e/2-1;
} else e/=2;
if(s%2==1) {
ans+=node[s];
s=s/2+1;
} else s/=2;
}
if(s!=e) ans += node[s]+node[e];
else ans+=node[s];
return ans;
}
//又ask_sum2()调用,对含有lazy标签的树进行区间求和操作,顺便更新部分节点 ///有误 (待修改)
void fun3(int o,int l,int r){
if(tl<=l && r<=tr ){
tn += node[o];
}else{
int mid = (l+r)/2;
if(add[o]) update(o,l,r);
if(tl<=mid) fun3(2*o,l,mid);
if(tr>=mid+1) fun3(2*o+1,mid+1,r);
node[o]=node[2*o] + node[o*2+1];
}
}
public:
Tree(int n) { //n:期待的叶子数量
leaf = n;
first = rank = sum = 1;
while(first < n) first*=2 , sum+=first, rank++ ;
node = new int[sum];
add = new int[sum];
memset(node,0,sizeof(int)*(sum+1));
memset(add,0,sizeof(int)*(sum+1));
}
~Tree() {
delete[] node;
delete[] add;
}
//为树叶a进行加减n运算,然后向上更新节点(单点赋值)
void add1(int a,int n) {
int t = first+a;
while(t) {
node[t]+=n;
t/=2;
}
}
//区间赋值操作,为l到r范围的树叶加减n运算,更新上面的节点。
void add2(int l,int r,int n) {
tl=l;
tr=r;
tn=n;
fun1(1,0,first-1); //注意是数组下标还是叶子下标 还有是first-1不是leaf-1
}
//区间赋值操作使用lazy减少工作量
void add3(int l,int r,int n){
tl = l;
tr = r;
tn = n;
fun2(1,0,first-1);
}
//当树使用lazy标签时使用特殊的区间求值操作,顺便更新部分节点
int ask_sum2(int l,int r){
tl = l;
tr = r;
tn = 0;
fun3(1,0,first-1);
return tn;
}
//输出从叶n到根节点的值
void test(int n) {
n = first+n;
while(n>0) {
cout<sum)return;
inoder(n*2);
printf("%d(%d,%d)\n",n,node[n],add[n]);
inoder(n*2+1);
return;
}
//前序遍历 ,先访问根节点,再左子树,再右子树。
void peroder(int n) {
if(n>sum) return;
printf("%d(%d)\n",n,node[n]);
peroder(n*2);
peroder(n*2+1);
return;
}
//后序遍历 ,先访问左子树,再右子树,最后根节点。
void lastoder(int n) {
if(n>sum) return;
lastoder(n*2);
lastoder(n*2+1);
printf("%d(%d)\n",n,node[n]);
return;
}
};
int main() {
Tree tree(8);
int temp[]= {-20,5,8,6,17,9,2,-1};
for(int i=0; i<8; i++) {
tree.add1(i,temp[i]);
}
int t,a,b,c;
while(cin>>t) {
if(t==1) tree.inoder(1);
if(t==2){
cin>>a>>b>>c;
tree.add3(a,b,c);
tree.inoder(1);
}
if(t==3){
cin>>a>>b;
cout<