看了很多次终于理解了一些
因为要经常用到左右孩子节点,所以先定义两个函数用于计算左右孩子的序号:
inline lc(int x){return x<<1;} //左儿子
inline rc(int x){return x<<1|1;}//右儿子
首先线段树是用二叉树的每一个节点表示了一个区间,最上面的节点表示了整个区间,然后下一层分别表示了上一层节点的一半,这样一层一层二分下去,直到最下面的叶子节点。
所以先看一下建树的代码:
void build(int p,int l,int r) //建树
{
if(l==r){
t[p]=a[l];
return ;
}
int mid=(l+r)>>1;
build(lc(p),l,mid);
build(rc(p),mid+1,r);
up(p);
}
这是一个递归的过程,从上往下二分,直到叶子节点的时候(l==r)就等于原数组中对应的数。然后每一次往下二分的时候,因为树的节点的值变化了,所以他的父节点也会变化,所以每次向下递归之后都要向上更新一次,就是up()函数:
void up(int p) //向上更新树
{
//t[p]=max(t[lc(p)],t[rc(p)]); //维护最值
t[p]=t[lc(p)]+t[rc(p)]; //维护和
}
然后说一下懒标记
更新线段树,节点需要从上更到下,修改n个值的话就会更新很多个节点。但有时候并不是所有更新的节点都会用到,所以用懒标记来节省操作。懒标记就是当树上的一个节点需要变化时,在这个节点打上懒标记,懒标记的值就是变化量,之后更新这个节点,然后就不往下更新了。在之后进行每一次更新或查询操作时,再向下更新一层。就是指在需要用到这个节点的时候才更新,否则不更新。这里懒标记用lazy[]数组,向下更新用down()函数:
void down(int p,int l,int r) //懒标记下传
{
if(lazy[p]){ //首先判断是否需要向下更新
//懒标记下传
lazy[lc(p)]+=lazy[p];
lazy[rc(p)]+=lazy[p];
//更新下一层的两个儿子节点
int mid=(l+r)>>1;
t[lc(p)]+=lazy[p]*(mid-l+1);
t[rc(p)]+=lazy[p]*(r-mid+1);
//当前这一层懒标记清0
lazy[p]=0;
}
}
之后结合懒标记进行更新操作:
void update(int xl,int xr,int x,int p,int l,int r)
{//xl,xr为要修改的区间
//x为变化的值
//p为当前节点编号
//l,r为当前区间
if(xl<=l && xr>=r){
//t[p]=x;
lazy[p]+=x; //管辖当前区间的节点加上懒标记
t[p]+=x*(r-l+1); //修改树上的节点
return ;
}
down(p,l,r); //下传一次懒标记
int mid=(l+r)>>1;
if(xl<=mid) update(xl,xr,x,lc(p),l,mid);
if(xr>mid) update(xl,xr,x,rc(p),mid+1,r);
up(p); //向上更新维护
}
查询操作:
int query(int xl,int xr,int l,int r,int p)
{//xl,xr为要修改的区间
//l,r为当前区间
//p为当前节点编号
if(xl<=l && r<=xr) return t[p];
down(p,l,r); //懒标记下传一层
int mid=(l+r)>>1;
int ans=0;
if(xl<=mid){
//ans=max(query(xl,xr,l,mid,lc(p)),ans); //查询最值的操作
ans+=query(xl,xr,l,mid,lc(p));
}
if(xr>mid){
//ans=max(query(xl,xr,mid+1,r,rc(p)),ans);
ans+=query(xl,xr,mid+1,r,rc(p));
}
return ans;
}
之后拼一拼就可以用了
以hdoj的1166为例:
#include <bits/stdc++.h>
using namespace std;
#define rg register
#define putln putchar('\n')
#define debug(x) cout<<"@ "<<(x)<<endl
#define rep(i,a,b) for(rg int i=a;i<=b;++i)
#define per(i,a,b) for(rg int i=a;i>=b;--i)
typedef long long ll;
const int MXN = 2e5+5;
int a[MXN];
int t[4*MXN],lazy[4*MXN]; //t是树,lazy是懒标记
int n,m,l,r,p;
string s;
inline lc(int x){return x<<1;} //左儿子
inline rc(int x){return x<<1|1;}//右儿子
void up(int p) //向上更新树
{
//t[p]=max(t[lc(p)],t[rc(p)]); //维护最值
t[p]=t[lc(p)]+t[rc(p)]; //维护和
}
void down(int p,int l,int r) //懒标记下传
{
if(lazy[p]){
//懒标记下传
lazy[lc(p)]+=lazy[p];
lazy[rc(p)]+=lazy[p];
//更新下一层的两个儿子节点
int mid=(l+r)>>1;
t[lc(p)]+=lazy[p]*(mid-l+1);
t[rc(p)]+=lazy[p]*(r-mid+1);
//当前这一层懒标记清0
lazy[p]=0;
}
}
void build(int p,int l,int r) //建树
{
if(l==r){
t[p]=a[l];
return ;
}
int mid=(l+r)>>1;
build(lc(p),l,mid);
build(rc(p),mid+1,r);
up(p);
}
void update(int xl,int xr,int x,int p,int l,int r)
{//xl,xr为要修改的区间
//x为变化的值
//p为当前节点编号
//l,r为当前区间
if(xl<=l && xr>=r){
//t[p]=x;
lazy[p]+=x; //管辖当前区间的节点加上懒标记
t[p]+=x*(r-l+1); //修改树上的节点
return ;
}
down(p,l,r); //下传一次懒标记
int mid=(l+r)>>1;
if(xl<=mid) update(xl,xr,x,lc(p),l,mid);
if(xr>mid) update(xl,xr,x,rc(p),mid+1,r);
up(p); //向上更新维护
}
int query(int xl,int xr,int l,int r,int p)
{//xl,xr为要修改的区间
//l,r为当前区间
//p为当前节点编号
if(xl<=l && r<=xr) return t[p];
down(p,l,r); //懒标记下传一层
int mid=(l+r)>>1;
int ans=0;
if(xl<=mid){
//ans=max(query(xl,xr,l,mid,lc(p)),ans); //查询最值的操作
ans+=query(xl,xr,l,mid,lc(p));
}
if(xr>mid){
//ans=max(query(xl,xr,mid+1,r,rc(p)),ans);
ans+=query(xl,xr,mid+1,r,rc(p));
}
return ans;
}
int main()
{
scanf("%d",&p);
rep(i,1,p){
memset(t,0,sizeof(t));
memset(lazy,0,sizeof(lazy));
scanf("%d",&n);
rep(j,1,n) scanf("%d",&a[j]);
build(1,1,n);
printf("Case %d:\n",i);
while(cin>>s&&s!="End"){
scanf("%d %d",&l,&r);
if(s=="Query") printf("%d\n",query(l,r,1,n,1));
if(s=="Add") update(l,l,r,1,1,n);
if(s=="Sub") update(l,l,-r,1,1,n);
}
}
return 0;
}