线段树定义:
struct node
{
int l/*区间左边界*/,r/*区间右边界*/,sum/*区间元素之和*/,lazy/*懒惰标记,下文会提到*/;
node(){l=r=sum=lazy=0;}//给每一个元素赋初值
}a[N];//N为总节点数
inline void update(int k)//更新节点k的sum
{
a[k].sum=a[k*2].sum+a[k*2+1].sum;
//很显然,一段区间的元素和等于它的子区间的元素和
}
初始化:
void build(int k/*当前节点的编号*/,int l/*当前区间的左边界*/,int r/*当前区间的右边界*/)
{
a[k].l=l,a[k].r=r;
if(l==r)//递归到叶节点
{
a[k].sum=number[l];//其中number数组为给定的初值
return;
}
int mid=(l+r)/2;//计算左右子节点的边界
build(k*2,l,mid);//递归到左儿子
build(k*2+1,mid+1,r);//递归到右儿子
update(k);//记得要用左右子区间的值更新该区间的值
}
单点修改:
void change(int k/*当前节点的编号*/,int x/*要修改节点的编号*/,int y/*要把编号为x的数字修改成y*/)
{
if(a[k].l==a[k].r){a[k].sum=y;return;}
//如果当前区间只包含一个元素,那么该元素一定就是我们要修改的。
//由于该区间的sum一定等于编号为x的数字,所以直接修改sum就可以了。
int mid=(a[k].l+a[k].r)/2;//计算下一层子区间的左右边界
if(x<=mid) change(k*2,x,y);//递归到左儿子
else change(k*2+1,x,y);//递归到右儿子
update(k);//记得更新点k的值,感谢qq_36228735提出此错误
}
区间修改(有lazy标记)(区间加x):
void changeSegment(int k,int l,int r,int x)
//当前到了编号为k的节点,要把[l..r]区间中的所有元素的值+x
{
if(a[k].l==l&&a[k].r==r)//如果找到了全部元素都要被修改的区间
{
a[k].sum+=(r-l+1)*x;
//更新该区间的sum
a[k].lazy+=x;return;
//懒惰标记叠加
}
int mid=(a[k].l+a[k].r)/2;
if(r<=mid) changeSegment(k*2,l,r,x);
//如果被修改区间完全在左区间
else if(l>mid) changeSegment(k*2+1,l,r,x);
//如果被修改区间完全在右区间
else changeSegment(k*2,l,mid,x),changeSegment(k*2+1,mid+1,r,x);
//如果都不在,就要把修改区间分解成两块,分别往左右区间递归
update(k);
//记得更新点k的值
}
区间查询(求和):
int query(int k,int l,int r)
//当前到了编号为k的节点,查询[l..r]的和
{
if(a[k].lazy) pushdown(k);
//如果当前节点被打上了懒惰标记,那么就把这个标记下传,这一句其实也可以放在下一语句的后面
if(a[k].l==l&&a[k].r==r) return a[k].sum;
//如果当前区间就是询问区间,完全重合,那么显然可以直接返回
int mid=(a[k].l+a[k].r)/2;
if(r<=mid) return query(k*2,l,r);
//如果询问区间包含在左子区间中
if(l>mid) return query(k*2+1,l,r);
//如果询问区间包含在右子区间中
return query(k*2,l,mid)+query(k*2+1,mid+1,r);
//如果询问区间跨越两个子区间
}
记得在查询之前下传标记!!!
下传标记:
void pushdown(int k)//将点k的懒惰标记下传
{
if(a[k].l==a[k].r){a[k].lazy=0;return;}
//如果节点k已经是叶节点了,没有子节点,那么标记就不用下传,直接删除就可以了
a[k*2].sum=(a[k*2].r-a[k*2].l+1)*a[k].lazy;
a[k*2+1].sum=(a[k*2+1].r-a[k*2+1].l+1)*a[k].lazy;
//给k的子节点重新赋值
a[k*2].lazy=a[k*2+1].lazy=a[k].lazy;
//下传点k的标记
a[k].lazy=0;//记得清空点k的标记
}