线段树

来源:互联网 发布:阿里云 bd 待遇 编辑:程序博客网 时间:2024/06/14 00:44

持续更新中。。。

线段树(1):点修改

用一个数组存节点的附加信息,并不需要存储线段的边界,因为每次递归都是严格二分的,所以每次递归到的节点范围都是确定的。节点数量最大为2*n,但数组大小要开到4*n。因为每一层不一定都是满的,虽然这些节点不存在,但依然在数组中占位置。如果当前节点下标为v,那么它的左孩子下标为v*2,右孩子v*2+1。

本线段树主要包含3个函数:建树,更新,查找。

以查找一段区间内的最大值为例。题目链接:I Hate It

(一)建树

从根节点往下递归,如果左边界(L)==右边界(R),则说明这是叶节点,给这个节点赋值,并回溯更新其所有的祖先节点。

void build(int v,int L,int R){    if(L==R)    {        int x;        scanf("%d",&x);        maxv[v]=x;    }    else    {        int mid=(L+R)/2;        build(v*2,L,mid);        build(v*2+1,mid+1,R);        maxv[v]=max(maxv[v*2],maxv[v*2+1]);    }}

(二)更新

与建树过程相似。用到了二分查找。
void updata(int v,int L,int R,int id,int score){    if(L==R && L==id)    {        maxv[v]=score;        return;    }    int mid=(L+R)/2;    if(id<=mid)        updata(v*2,L,mid,id,score);    else        updata(v*2+1,mid+1,R,id,score);    maxv[v]=max(maxv[v*2],maxv[v*2+1]);}

(三)查询

二分查找。
int ask(int v,int L,int R,int ql,int qr){    if(ql<=L && R<=qr) return maxv[v];    int mid=(L+R)/2;    if(qr<=mid) return ask(v*2,L,mid,ql,qr);    else if(ql>mid) return ask(v*2+1,mid+1,R,ql,qr);    else if(ql<=mid && mid<=qr) return max(ask(v*2,L,mid,ql,qr),ask(v*2+1,mid+1,R,ql,qr));}

线段树(2):区间修改

区间修改是对区间内的所有节点进行修改,所以如果还用点修改的线段树来做,效率会非常低。这里我们引入了一个延迟更新标记lazy(又称懒惰标记)。顾名思义,这个标记非常懒,更新节点时能停则停,即这个区间包含在需要更新的区间内,就不会更新下面的子节点了。除非需要访问或修改它的子节点,它才会往下更新。为何我想起了检查卫生时的场景?不得不说,这样效率非常高。
代码在点修改线段树的基础上多维护一个lazy数组。以区间求和为例。题目链接:Just a Hook

(一)建树

初始化sumv与lazy。
void build(int v,int L,int R){    lazy[v]=-1;    if(L==R)        sumv[v]=1;    else    {        int mid=(L+R)/2;        build(v*2,L,mid);        build(v*2+1,mid+1,R);        sumv[v]=sumv[v*2]+sumv[v*2+1];    }}

(二)更新区间

如果访问到当前节点有lazy标记,则pushdown,将标记推到其子节点上去。不要忘了取消当前节点的lazy标记。如果节点的区间包含在要更新的区间内,则更新区间值,加上lazy标记。
void updata(int v,int L,int R,int ql,int qr,int qn){    if(ql<=L && R<=qr)    {        sumv[v]=qn*(R-L+1);        lazy[v]=qn;        return;    }    if(lazy[v]!=-1)        pushdown(v,L,R);    int mid=(L+R)/2;    if(ql<=mid)        updata(v*2,L,mid,ql,qr,qn);    if(qr>mid)        updata(v*2+1,mid+1,R,ql,qr,qn);    sumv[v]=sumv[v*2]+sumv[v*2+1];}

(三)pushdown

将lazy标记推到其子节点上,并更新其子节点的sumv值。
void pushdown(int v,int L,int R){    sumv[v*2]=lazy[v]*((R-L+2)/2);    lazy[v*2]=lazy[v];    sumv[v*2+1]=lazy[v]*((R-L+1)/2);    lazy[v*2+1]=lazy[v];    lazy[v]=-1;}

(四)查询

虽然这一题不需要query,但我还是写了一个模板。就多了一个pushdown。
int query(int v,int L,int R,int ql,int qr){    int ret=0;    if(ql<=L && R<=qr)        return sumv[v];    int mid=(L+R)/2;    if(lazy[v]!=-1)        pushdown(v,L,R);    if(ql<=mid)        ret+=query(v*2,L,mid,ql,qr);    if(qr>mid)        ret+=query(v*2+1,mid+1,R,ql,qr);    return ret;}

原创粉丝点击