zoukankan      html  css  js  c++  java
  • 线段树

    转自     http://www.cnblogs.com/aiguona/p/7337938.html

    线段树

    一、基本概念

    1、线段树是一棵二叉搜索树,它储存的是一个区间的信息。

    2、每个节点以结构体的方式存储,结构体包含以下几个信息:

         区间左端点、右端点;(这两者必有)

         这个区间要维护的信息(事实际情况而定,数目不等)。

    3、线段树的基本思想:二分

    4、线段树一般结构如图所示:

    5、特殊性质:

    由上图可得,

    1、每个节点的左孩子区间范围为[l,mid],右孩子为[mid+1,r]

    2、对于结点k,左孩子结点为2*k,右孩子为2*k+1,这符合完全二叉树的性质

    二、线段树的基础操作

    注:以下基础操作均以引例中的求和为例,结构体以此为例:

    struct node
    {
        int left,right;//左右
        int weight;//权值
        int flag;//延迟标记
    } tree[400001];
    线段树的基础操作主要有5个:
    建树、单点查询、单点修改、区间查询、区间修改。
    

    1、建树,即建立一棵线段树

       ① 主体思路:

          a、对于二分到的每一个结点,给它的左右端点确定范围。

                         b、如果是叶子节点,存储要维护的信息。

                         c、状态合并。

       ②代码

    void build(int k,int ll,int rr)//建树,k是父亲节点,ll、rr左右孩子
    {
        tree[k].left=ll,tree[k].right=rr;
        if(tree[k].left==tree[k].right)//如果是叶子结点,直接赋值
        {
            scanf("%d",&tree[k].weight);
            return;
        }
        int m=(ll+rr)/2;
        build(k*2,ll,m);
        build(k*2+1,m+1,rr);
        tree[k].weight=tree[k*2].weight+tree[k*2+1].weight;//求区间和,所以父亲结点是左右孩子的和
    }

    ③注意

         a.结构体要开4倍空间,因为叶子节点的存储并不是按顺序存储的。

         b.千万不要漏了return语句,因为到了叶子节点不需要再继续递归了。 

    2、单点查询,即查询一个点的状态,设待查询点为x

       ①主体思路:与二分查询法基本一致,如果当前枚举的点左右端点相等,即叶子节点,就是目标节点。如果不是,因为这是二分法,所以设查询位置为x,当前结点区间范围为了l,r,中点为mid,则如果x<=mid,则递归它的左孩子,否则递归它的右孩子

         ②代码

    void ask_point(int k)//单点查询
    {
        if(tree[k].left==tree[k].right)
        {
            ans=tree[k].weight;
            return ;
        }
        if(tree[k].flag) down(k);
        int m=(tree[k].left+tree[k].right)/2;
        if(x<=m) ask_point(k*2);
        else ask_point(k*2+1);
    }

     3、单点修改,即更改某一个点的状态。用引例中的例子,对第x个数加上y

         ①主体思路

         结合单点查询的原理,找到x的位置;根据建树状态合并的原理,修改每个结点的状态。

          ②代码

    void change_point(int k)//单点修改,第x个数加上y
    {
        if(tree[k].left==tree[k].right)
        {
            tree[k].weight+=y;
            return;
        }
        if(tree[k].flag) down(k);
        int m=(tree[k].left+tree[k].right)/2;
        if(x<=m) change_point(k*2);
        else change_point(k*2+1);
        tree[k].weight=tree[k*2].weight+tree[k*2+1].weight;
    }
    4、区间查询,即查询一段区间的状态,在引例中为查询区间[x,y]的和
    ①主体思路
    

         

        ②代码

    void ask_interval(int k)//区间查询,查询区间和
    {
        if(tree[k].left>=a&&tree[k].right<=b)
        {
            ans+=tree[k].weight;
            return;
        }
        if(tree[k].flag) down(k);
        int m=(tree[k].left+tree[k].right)/2;
        if(a<=m) ask_interval(k*2);
        if(b>m) ask_interval(k*2+1);
    }
    5、区间修改,即修改一段连续区间的值,我们已给区间[a,b]的每个数都加x为例讲解
    

          Ⅰ.引子

           有人可能就想到了:

           修改的时候只修改对查询有用的点。

           对,这就是区间修改的关键思路。

          为了实现这个,我们引入一个新的状态——延迟标记

         Ⅱ 延迟标记

           1、直观理解:“延迟”标记,用到它才动,不用它就不动。

           2、作用:存储到这个节点的修改信息,暂时不把修改信息传到子节点。

           3、实现思路(重点):

               a.原结构体中增加新的变量,存储这个延迟标记。

               b.递归到这个节点时,只更新这个节点的状态,并把当前的更改值累积到标记中。

               c.当需要递归这个节点的子节点时,标记下传给子节点。这里不必管用哪个子节点,两个都传下去。

               d.下传操作:

                   3部分:①当前节点的延迟标记累积到子节点的延迟标记中。

                                ②修改子节点状态。在引例中,就是原状态+子节点区间点的个数*父节点传下来的延迟标记。 

                                ③父节点延迟标记清0。

         延迟标记下穿代码:flag为懒标记,其余变量与前面含义一致。

    延迟标记代码:

    void down(int k)//延迟标记下传
    {
        tree[k*2].flag+=tree[k].flag;
        tree[k*2+1].flag+=tree[k].flag;
        tree[k*2].weight+=tree[k].flag*(tree[k*2].right-tree[k*2].left+1);
        tree[k*2+1].weight+=tree[k].flag*(tree[k*2+1].right-tree[k*2+1].left+1);
        tree[k].flag=0;
    }
    区间修改代码 
    
    void change_interval(int k)//区间修改,把a-b之间数改成k
    {
        if(tree[k].left>=a&&tree[k].right<=b)
        {
            tree[k].weight+=(tree[k].right-tree[k].left+1)*y;
            tree[k].flag+=y;
            return;
        }
        if(tree[k].flag) down(k);
        int m=(tree[k].left+tree[k].right)/2;
        if(a<=m) change_interval(k*2);
        if(b>m) change_interval(k*2+1);
        tree[k].weight=tree[k*2].weight+tree[k*2+1].weight;
    }

    三、线段树五种操作模板

    //#include <bits/stdc++.h>
    #include <cstdlib>
    #include <cstring>
    #include <cstdio>
    #include <cmath>
    #include <iostream>
    #include <algorithm>
    #include <string>
    #include <queue>
    #include <stack>
    #include <map>
    #include <set>
    
    #define IO ios::sync_with_stdio(false);
        cin.tie(0);
        cout.tie(0);
    
    typedef long long LL;
    const long long INF = 0x3f3f3f3f;
    const long long mod = 1e9+7;
    const double PI = acos(-1.0);
    const int maxn = 100000;
    const char week[7][10]= {"Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"};
    const char month[12][10]= {"Janurary","February","March","April","May","June","July",
                               "August","September","October","November","December"
                              };
    const int daym[2][13] = {{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
        {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
    };
    const int dir4[4][2] = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};
    const int dir8[8][2] = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}, {1, 1}, {-1, -1}, {1, -1}, {-1, 1}};
    
    using namespace std;
    LL n,m;//n个结点,m种操作,操作为p
    LL ans;//存储答案
    struct node
    {
        LL left,right;//左右
        LL weight;//权值
        LL flag;//延迟标记
    } tree[410000];
    void build(LL k,LL ll,LL rr)//建树,k是父亲节点,ll、rr左右孩子
    {
        tree[k].left=ll,tree[k].right=rr;
        if(tree[k].left==tree[k].right)//如果是叶子结点,直接赋值
        {
            scanf("%lld",&tree[k].weight);
            return;
        }
        LL m=(ll+rr)/2;
        build(k*2,ll,m);
        build(k*2+1,m+1,rr);
        tree[k].weight=tree[k*2].weight+tree[k*2+1].weight;//求区间和,所以父亲结点是左右孩子的和
    }
    void down(LL k)//延迟标记下传
    {
        tree[k*2].flag+=tree[k].flag;
        tree[k*2+1].flag+=tree[k].flag;
        tree[k*2].weight+=tree[k].flag*(tree[k*2].right-tree[k*2].left+1);
        tree[k*2+1].weight+=tree[k].flag*(tree[k*2+1].right-tree[k*2+1].left+1);
        tree[k].flag=0;
    }
    void ask_point(LL k,LL x)//单点查询
    {
        if(tree[k].left==tree[k].right)
        {
            ans=tree[k].weight;
            return ;
        }
        if(tree[k].flag) down(k);
        LL m=(tree[k].left+tree[k].right)/2;
        if(x<=m) ask_point(k*2,x);
        else ask_point(k*2+1,x);
    }
    void change_point(LL k,LL x,LL y)//单点修改,第x个数加上y
    {
        if(tree[k].left==tree[k].right)
        {
            tree[k].weight+=y;
            return;
        }
        if(tree[k].flag) down(k);
        LL m=(tree[k].left+tree[k].right)/2;
        if(x<=m) change_point(k*2,x,y);
        else change_point(k*2+1,x,y);
        tree[k].weight=tree[k*2].weight+tree[k*2+1].weight;
    }
    void ask_interval(LL k,LL a,LL b)//区间查询,查询区间和
    {
        if(tree[k].left>=a&&tree[k].right<=b)
        {
            ans+=tree[k].weight;
            return;
        }
        if(tree[k].flag) down(k);
        LL m=(tree[k].left+tree[k].right)/2;
        if(a<=m) ask_interval(k*2,a,b);
        if(b>m) ask_interval(k*2+1,a,b);
    }
    void change_interval(LL k,LL a,LL b,LL y)//区间修改,把a-b之间数改成k
    {
        if(tree[k].left>=a&&tree[k].right<=b)
        {
            tree[k].weight+=(tree[k].right-tree[k].left+1)*y;
            tree[k].flag+=y;
            return;
        }
        if(tree[k].flag) down(k);
        LL m=(tree[k].left+tree[k].right)/2;
        if(a<=m) change_interval(k*2,a,b,y);
        if(b>m) change_interval(k*2+1,a,b,y);
        tree[k].weight=tree[k*2].weight+tree[k*2+1].weight;
    }
    int main()
    {
        scanf("%d",&n);//n个节点
        scanf("%d",&m);//m种操作
        build(1,1,n);//建树,传值根节点1,左孩子1,有孩子n
        for(int i=1; i<=m; i++)
        {
            int p;
            getchar();
            scanf("%d",&p);
            ans=0;
            /*以下操作传值过去的为根节点1*/
            if(p==1)
            {
                LL x;
                scanf("%lld",&x);
                ask_point(1,x);//单点查询,输出第x个数
                printf("%lld",ans);
            }
            else if(p==2)
            {
                LL x,y;
                scanf("%lld%lld",&x,&y);
                change_point(1,x,y);//单点修改
            }
            else if(p==3)
            {
                LL a,b;
                scanf("%lld%lld",&a,&b);//区间查询
                ask_interval(1,a,b);
                printf("%lld
    ",ans);
            }
            else if(p==4)//每段都加上一个数
            {
                LL a,b,y;
                scanf("%lld%lld%lld",&a,&b,&y);//区间修改
                change_interval(1,a,b,y);
            }
        }
    }
    
     
  • 相关阅读:
    C++进阶--placement new/delete
    mac_Mac item2常用快捷键
    linux_安装_安装编译phantomjs 2.0的方法_转
    linux_删除空文件(大小等于0的文件)的方法
    linux_根据关键词_路径下递归查找code
    linux_shell_获取日期相关
    php_中替换换行符
    linux_增加用户组_删除用户
    数据分析入门
    linux_vim_最佳快捷键
  • 原文地址:https://www.cnblogs.com/zcy19990813/p/9702728.html
Copyright © 2011-2022 走看看