线段树

只谈情不闲聊 提交于 2019-11-28 11:26:13

 

1.概念:

线段树将一个区间划分为多个单元区间,每个单元区间对应线段树中的一个叶子结点。

 

2.功能:

 主要用于高效解决连续区间的动态查询问题。

3.性质:

   根节点【m,n】 , 左子树【m,(m+n)/2】,右子树【(m+n)/2+1,n】 

4.离散化

  离散化是为了解决数据范围太大的情况,不改变其相对大小,用小数来替代大数,                                                            

个人模板:

线段树的定义:

const int maxn=200005;
///线段树的定义
int a[maxn];
struct node
{
    int l,r;
    int CC;///CC可以代表sum,min,max等,依据自己定义,
}tree[maxn*4];

 

建树

void build_tree(int root,int l,int r)
{
    tree[root].l=l;
    tree[root].r=r;
    if(l==r)
    {
        tree[root].CC=a[l];
        return;
    }
    int mid=(l+r)/2;
    build_tree(root*2,l,mid);
    build_tree(root*2+1,mid+1,r);
    tree[root].CC=max(tree[root*2].CC,tree[root*2+1].CC);
}

 

单点更新(是区间更新的特例)

update(root , x , v) 即从根节点一直向下直到找到更新区间(Ax,Ax)即叶子节点

///单点更新
void update(int root,int where,int value)
{
    int a,b,mid;
    a=tree[root].l;
    b=tree[root].r;
    if(where==a&&where==b)
    {
        tree[root].CC=value;
        return;
    }
    mid=(a+b)>>1;
    if(where<=mid)
        update(root*2,where,value);
    else
        update(root*2+1,where,value);

    tree[root].CC=max(tree[root*2].CC,tree[root*2+1].CC);
}

区间查询

query(root ,l ,r )查询即从根节点处一直向下,不断拆分区间,不重不漏,比如查询[2,5]的min值,即[2,5]=[2]+[3,3]+[4,4]+[5,5]

int query(int root,int x,int y)
{
    int a,b,mid;
    a=tree[root].l;
    b=tree[root].r;
    if(a>=x&&b<=y)  ///当前节点完全被包含在查询区间内,即 x———a—————b————y
        return tree[root].CC;
    mid=(a+b)>>1;
    int res=-999;
    if(x<=mid)///查询左子树
        res=max(res,query(root*2,x,y));
    if(y>=mid+1)///查询右子树
        res=max(res,query(root*2+1,x,y));
    return res;
}

区间修改(重要)

区间更新是指更新某个区间内 的 叶子节点的值 ,而涉及到的叶子节点的值的区间不止一个,而且叶子节点会回溯到相应的父亲节点,若是每次更新,则耗费时间,则需要用到延迟标记

 

延迟标记:

每个节点新增加一个标记,记录这个节点是否进行了某种修改(这种修改操作会影响其子节点),对于任意区间的修改,先按照区间查询的方式将其划分成线段树中的节点,然后修改这些节点的信息,并给这些节点标记上代表这种修改操作的标记mark

当我们再次修改和查询的时候,如果我们到了一个节点p,并且决定考虑其子节点,那么我们就要看节点p是否被标记,如果有,就要按照标记修改其子节点的信息,并且给子节点都标上相同的标记,同时消掉节点p(即新节点的父亲节点)的标记,直到更新到完全包含于所更新区间的区间就停止。

即线段树的延迟标记就是标记以后以便下一次查询,遇到标记时更新此节点的子区间及其标记mark。如果需要继续向下查询,继续这样下去,直到查询结束。

 

const int maxn=100005;
int a[maxn];

struct node
{
    int r,l;
    int CC;
    int mark;
}tree[maxn<<2];

void pushup(int root)
{
    if(tree[root].l==tree[root].r)
        return;
    tree[root].CC=tree[root*2].l+tree[root*2+1].r;
}

void pushdown(int root,int l,int r)
{
    int mid=(l+r)/2;
    ///更新左儿子
    tree[root*2].mark+= tree[root].mark;
    tree[root*2].CC+=(mid-l+1)*tree[root].mark;

    ///更新右儿子
    tree[root*2+1].mark+=tree[root].mark;
    tree[root*2+1].CC+=(r-(mid+1)+1)*tree[root].mark;

    ///消除父亲节点的标记
    tree[root].mark=0;
}
///建树
void build_tree(int root,int l,int r)
{
    tree[root].mark=0;
    tree[root].l=l;
    tree[root].r=r;

    if(l==r)
    {
        tree[root].CC=a[l];
        return ;
    }
    int mid=(l+r)>>1;

    build_tree(root*2,l,mid);
    build_tree(root*2+1,mid+1,r);

    pushup(root);
}

///区间更新:在(L,R)区间的每个数上加上一个c
void update(int root,int l,int r,int L,int R,int c)
{
    if(L<=l&&R>=r)
    {
        tree[root].CC+=(r-l+1)*c;
        tree[root].mark+=c;
        return;
    }
    if(tree[root].mark!=0)///延迟标记
    {
        pushdown(root,l,r);
    }
    int mid=(l+r)>>1;

    if(L<=mid)///查询左子树
        update(root*2,l,mid,L,R,c);
    if(R>mid)
        update(root*2+1,mid+1,r,L,R,c);
    pushup(root);
}

///区间查询 (L,R)区间的数的和
int query(int root,int l,int r,int L,int R)
{
    if(L<=l&&R>=r)
    {
        return tree[root].CC;
    }
    if(tree[root].mark!=0)///如果有标记就需要往下更新
    {
        pushdown(root,l,r);
    }
    int mid=(l+r)/2;
    int fin=0;
    if(L<=mid)
    {
        fin+=query(root*2,l,mid,L,R);
    }
    if(mid<R)
    {
        fin+=query(root*2+1,mid+1,r,L,R);
    }
    return fin;
}

 

 

 

  

 

 

 

 

 

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!