线段树简介

来源:互联网 发布:ansible windows 编辑:程序博客网 时间:2024/06/08 11:43

要看lazy-tag的请直接跳到区间改值

算法用途

用处很多啊,主要是维护某一区间的值,比如最大值,区间和什么的。支持修改和查询操作。

算法思想

对于每个区间[l,r],令[l,(l+r)/2]为该区间的左儿子,[(l+r)/2+1,r]为该节点的右儿子。对于每个查询/修改,做完当前节点后继续做其子节点,若当前节点超过查询/修改操作,直接返回。查询和修改均为递归操作,单次查询/修改时间O(logn)。

算法关键

建树

区间怎么建树啊??
标号啊!
像二叉树那样给每个区间标个号,区间标号num的左右儿子分别为num*2和num*2+1,一直到该区间左右边界位置相等(l==r)。
然后递归建树啊!
先定义这样一个struct:

struct tree{    int l;//左端点    int r;//右端点    int sum;//当前节点权值};

然后开个数组存树:

tree t[MAXN*4+5];//要开4倍MAXN。

再递归建树即可:

//这里是区间求和void build(int l,int r,int num){//l为当前区间左端点,r为当前区间右端点,num为当前区间编号    t[num].l=l;//左端点赋值    t[num].r=r;//右端点赋值    if (l==r){//如果是叶子节点            t[num].sum=a[l];//它的总和即为它自己            return;    }    //分别向左右递归    build(l,(l+r)/2,num*2);    build((l+r)/2+1,r,num*2+1);    t[num].sum=t[num*2].sum+t[num*2+1].sum;//当前节点的值即为它的两个孩子节点的值之和}

单点修改操作

单点修改的话还是比较好理解的,从1号节点(就是区间[1,n])开始递归,先根据区间范围判断是递归左孩子还是右孩子,直到递归到要修改的节点,修改完毕后回溯,重新计算经过节点的值即可。

void change(int p,int w,int num){//p为要修改的点(区间上的),w为修改的值,num为当前区间的编号    if (t[num].l==t[num].r){//如果找到该节点        t[num].sum=w;        return;    }    if (p<=(t[num].l+t[num].r)/2)//判断p在哪个区间内        change(p,w,num*2);    else        change(p,w,num*2+1);    t[num].sum=t[num*2].sum+t[num*2+1].sum;//重新计算该节点的值}

查询

设我们要查询的区间为[l,r],对于当前区间,判断其是否仍有被查询区间覆盖的部分,若没有就返回。如果该区间被查询区间完全覆盖,直接返回该节点的值即可。

int search(int l,int r,int num){    if (t[num].l>r||t[num].r<l)//如果当前区间并没有被查询区间覆盖        return 0;    if (t[num].l>=l&&t[num].r<=r)//如果当前区间完全被查询区间覆盖        return t[num].sum;    return search(l,r,num*2)+search(l,r,num*2+1);}

模板

以hihocoder1077为例:

#include<cstdio>#include<cstring>#include<algorithm>#define MAXN 1000000using namespace std;struct tree{    int l;    int r;    int sum;};int n,m;tree t[MAXN*4+5];int a[MAXN+5];void build(int l,int r,int num){    t[num].l=l;    t[num].r=r;    if (l==r){            t[num].sum=a[l];            return;    }    build(l,(l+r)/2,num*2);    build((l+r)/2+1,r,num*2+1);    t[num].sum=t[num*2].sum+t[num*2+1].sum;}void change(int p,int w,int num){    if (t[num].l==t[num].r){        t[num].sum=w;        return;    }    if (p<=(t[num].l+t[num].r)/2)        change(p,w,num*2);    else        change(p,w,num*2+1);    t[num].sum=t[num*2].sum+t[num*2+1].sum;}int search(int l,int r,int num){    if (t[num].l>r||t[num].r<l)        return 0;    if (t[num].l>=l&&t[num].r<=r)        return t[num].sum;    return search(l,r,num*2)+search(l,r,num*2+1);}int main(){    scanf("%d",&n);    for (int i=1;i<=n;i++)        scanf("%d",&a[i]);    build(1,n,1);    scanf("%d",&m);    for (int i=1;i<=m;i++){        int flag,x,y;        scanf("%d%d%d",&flag,&x,&y);        if (!flag)            printf("%d\n",search(x,y,1));        else            change(x,y,1);    }    return 0;}

区间修改

很多时候题目并不只限于单点修改,而是需要修改某一个连续区间的值,这时如果和上面一样做的话,单次修改的复杂度为O(L*logn)(L为区间长度),还不如直接暴力O(L)修改。这个时候我们就要用到一个神奇的东西:Lazy-Tag。

Lazy-Tag,懒惰标记,对于修改[l,r],如果当前区间被完全覆盖,我们就不要推下去,而是懒懒地给这个区间打上标记,并更新它的权值。一般标记内容就是修改的值。这样就可以免去之后的工作。

像这样:

if (t[num].l>=l&&t[num].r<=r){//如果当前区间被完全覆盖    lazy[num]=w;//打上标记    t[num].sum=(t[num].r-t[num].l+1)*lazy[num];//更新权值(这里是把一段区间的值都改为w,询问区间和)    return;}

但是查询的时候,或者要修改它子区间的值的时候怎么办呢?

当我们做到一个节点时,立即把懒惰标记传递给他的儿子,并更新它的权值。这样就可以保证查询/修改过的都是更新过的。这个操作可以写一个函数来实现。

void pushdown(int num){    lazy[num*2]=lazy[num*2+1]=lazy[num];//把标记传给子节点    t[num*2].sum=(t[num*2].r-t[num*2].l+1)*lazy[num*2];//更新权值    t[num*2+1].sum=(t[num*2+1].r-t[num*2+1].l+1)*lazy[num*2+1];    lazy[num]=0;//清零}

然后我们就可以写出查询和修改的代码了:

void change(int l,int r,int w,int num){//修改    if (t[num].l>r||t[num].r<l)        return;    if (t[num].l>=l&&t[num].r<=r){//如果区间被完全覆盖        lazy[num]=w;        t[num].sum=(t[num].r-t[num].l+1)*lazy[num];        return;    }    if (lazy[num])        pushdown(num);    change(l,r,w,num*2);    change(l,r,w,num*2+1);    t[num].sum=t[num*2].sum+t[num*2+1].sum;}
int search(int l,int r,int num){//查询    if (t[num].l>r||t[num].r<l)        return 0;    if (t[num].l>=l&&t[num].r<=r)        return t[num].sum;    if (lazy[num])        pushdown(num);    return search(l,r,num*2)+search(l,r,num*2+1);}

模板

以hihocoder1078为例:

#include<cstdio>#include<cstring>#include<algorithm>#define MAXN 100000using namespace std;struct tree{    int l;    int r;    int sum;};int n,m;tree t[4*MAXN+5];int a[MAXN+5],lazy[4*MAXN+5];void build(int l,int r,int num){    t[num].l=l;    t[num].r=r;    if (l==r){            t[num].sum=a[l];            return;    }    build(l,(l+r)/2,num*2);    build((l+r)/2+1,r,num*2+1);    t[num].sum=t[num*2].sum+t[num*2+1].sum;}void pushdown(int num){    lazy[num*2]=lazy[num*2+1]=lazy[num];    t[num*2].sum=(t[num*2].r-t[num*2].l+1)*lazy[num*2];    t[num*2+1].sum=(t[num*2+1].r-t[num*2+1].l+1)*lazy[num*2+1];    lazy[num]=0;}void change(int l,int r,int w,int num){    if (t[num].l>r||t[num].r<l)        return;    if (t[num].l>=l&&t[num].r<=r){        lazy[num]=w;        t[num].sum=(t[num].r-t[num].l+1)*lazy[num];        return;    }    if (lazy[num])        pushdown(num);    change(l,r,w,num*2);    change(l,r,w,num*2+1);    t[num].sum=t[num*2].sum+t[num*2+1].sum;}int search(int l,int r,int num){    if (t[num].l>r||t[num].r<l)        return 0;    if (t[num].l>=l&&t[num].r<=r)        return t[num].sum;    if (lazy[num])        pushdown(num);    return search(l,r,num*2)+search(l,r,num*2+1);}int main(){    scanf("%d",&n);    for (int i=1;i<=n;i++)        scanf("%d",&a[i]);    build(1,n,1);    scanf("%d",&m);    for (int i=1;i<=m;i++){        int flag;        scanf("%d",&flag);        if (!flag){            int x,y;            scanf("%d%d",&x,&y);            printf("%d\n",search(x,y,1));        }        else{            int x,y,w;            scanf("%d%d%d",&x,&y,&w);            change(x,y,w,1);        }    }    return 0;}
原创粉丝点击