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

    【线段树的定义】

         有时候我们经常会碰到一些跟区间有关的问题,比如给一些区间线段求并区间的长度,或者并区间的个数等等。这些问题的描述都非常简单,但是通常情况下数据范围会非常大,而朴素方法的时间复杂度过高,导致不能在规定时间内得到问题的解。这时,我们需要一种高效的数据结构来处理这样的问题,我们介绍一种基于分治思想的数据结构--线段树。
         从简单说起,线段树其实可以理解成一种特殊的二叉树。但是这种二叉树较为平衡,和静态二叉树一样,都是提前已经建立好的树形结构。针对性强,所以效率要高。线段树是建立在线段的基础上,每个结点都代表了一条线段 [a , b]。长度为1的线段成为元线段。非元线段都有两个子结点,左结点代表的线段为[a , (a + b ) / 2],右结点代表的线段为[( a + b ) / 2 +1, b]。
    下面看一个实例:
    上图就是范围为[1,10]的线段树。根节点代表[1,10]区间,左孩子[1,5],右孩子[6,10]....依次类推,叶子节点代表的是长度为1的区间。
     【简单线段树的建立,查询,更新】
    一 建立寻找区间最大值的线段树
    1.线段树的建立:
     1 void bulid_tree(int rt,int L,int R)//构造线段树的过程相当于后序遍历
     2 {
     3     if(L==R) cin>>Max[rt];//说明到了叶节点,输入叶节点的值,Max数组存储的是各区间段的最大值
     4     else
     5     {
     6         int mid=(L+R)/2;
     7         build_tree(2*rt,L,mid);//递归构造左子树
     8         build_tree(2*rt+1,mid+1,R);//递归构造右子树
     9         Max[rt]=max(Max[2*rt],Max[2*rt+1]);//寻找根节点的值,左右儿子的较大值
    10     }
    11 }

    2.线段树的更新:类似二分

     1 void  update(int r,int L,int R)
     2 {
     3     int mid=(L+R)/2;
     4     if(L==R) Max[r]=v;//如果该点就是叶节点的话就把值更新,此时通过二分就找到了p,且p=L=R
     5     else
     6     {
     7        if(p<=mid) update(2*r,L,mid);//如果查询点P在区间以左,则递归查询左区间
     8        else  update(2*r+1,mid+1,R);//如果查询点p在区间以右,则递归查询右区间
     9        Max[r]=max(Max[2*r],Max[2*r+1]);//更新并求得父节点最大值
    10     }
    11 }

    3.线段树的查询:(以下函数只支持单点查询)

    1 int query(int r,int ql,int qr)//查询操作
    2 {
    3     int mid=(L+R)/2;
    4     if(ql<=L&&qr>=R) return Max[r];//[ql,qr]包含区间[L,R]
    5     else if(qr<=mid) return query(2*r,L,mid);//[ql,qr]在区间[L,R]以左
    6     else if(ql>mid) return query(2*r+1,mid+1,R);//[ql,qr]在区间[L,R]以右
    7     else
    8         return max(query(2*r,L,mid),query(2*r+1,mid+1,R));//ql在区间[L,R]中点以左,qr在区间中点以右
    9 }

    【复杂线段树的建立】

    维护一个只有0和1的整数数列,支持四种操作:
    ①将[x,y]区间内的整数都变为0;
    ②将[x,y]区间内的整数都变为1;
    ③将[x,y]区间内所有0都变成1,所有1都变成0。
    ④查询区间[x,y]内1的个数。
    一共有N个整数共执行M次操作。(N, M均为10^5级别)
     
    1.线段树的建立:
    
    
    // 更新父节点信息的update函数
    void update(int cur)
    {
        int ls = cur << 1, rs = cur << 1 | 1;
        ones[cur] = ones[ls] + ones[rs];
    }
     
    // 建立线段树
    void build(int cur, int x, int y)
    {
        int mid = (x + y) >> 1, ls = cur << 1, rs = cur << 1 | 1;
        to[cur] = -1, rev[cur] = 0;//to数组是用来判断是否被懒惰标记,rev数组是用来判断是否被反转
        if(x == y)
        {
            ones[cur] = a[x]; // a[]中存放的是序列
            return ;
        }
        build(ls, x, mid);
        build(rs, mid + 1, y);
        update(cur);
    }
     
    // 调用build建立线段树
    build(1, 1, N);
     
     

    2.线段树的修改:

    // 下传lazy标记的pushdown函数
    void pushdown(int cur, int x, int y)
    {
        int mid = (x + y) >> 1, ls = cur << 1, rs = cur << 1 | 1;
        if(to[cur] != -1)//已经被标记
        {
            to[ls] = to[rs] = to[cur];//传递到子节点
            rev[ls] = rev[rs] = 0;//标记为未被反转
            ones[ls] = to[cur] * (mid - x + 1);//计算左儿子的值
            ones[rs] = to[cur] * (y - mid);//计算右儿子的值
            to[cur] = -1;//父节点消除懒惰标记
        }
        if(rev[cur])//如果父节点已经被反转
        {
            rev[ls] ^= 1, rev[rs] ^= 1;//儿子节点也跟随反转
            ones[ls] = mid - x + 1 - ones[ls];//因为节点值都为1或者0,那么便可以用mid-x+1表示全为1的情况,那么减去ones[ls]便可以得到反转以后的数据
            ones[rs] = y - mid - ones[rs];//理由同上
            rev[cur] = 0;//已经传递到儿子,把父节点置为未反转
        }
    }
    // 将[s,t]区间内所有整数赋值成0或者1
    void change(int cur, int x, int y, int s, int t, int v)
    {
        int mid = (x + y) >> 1, ls = cur << 1, rs = cur << 1 | 1;
        if(x >= s && y <= t)
        {
            ones[cur] = v * (y - x + 1);
            to[cur] = v, rev[cur] = 0;
            return ;
        }
        pushdown(cur, x, y);
        if(mid >= s)//这里注意理解,如果所求区间在原有区间的中点左右分布,那么两个if可以同时执行
            change(ls, x, mid, s, t, v);
        if(mid + 1 <= t)
            change(rs, mid + 1, y, s, t, v);
        update(cur);
    }

    3.线段树的反转:

    // 将[s,t]区间内所有0变为1,所有1变为0
    void reverse(int cur, int x, int y, int s, int t)
    {
        int mid = (x + y) >> 1, ls = cur << 1, rs = cur << 1 | 1;
        if(x >= s && y <= t)
        {
            ones[cur] = y - x + 1 - ones[cur];
            rev[cur] ^= 1;
            return ;
        }
        pushdown(cur, x, y);
        if(mid >= s)
            reverse(ls, x, mid, s, t);
        if(mid + 1 <= t)
            reverse(rs, mid + 1, y, s, t);
        update(cur);
    }

    4.线段树的查询:

    // 查询区间[s,t]内1的个数
    void query(int cur, int x, int y, int s, int t, int &ans)
    {
        int mid = (x + y) >> 1, ls = cur << 1, rs = cur << 1 | 1;
        if(x >= s && y <= t)
        {
            ans += ones[cur];
            return ;
        }
        pushdown(cur, x, y);
        if(mid >= s)
            query(ls, x, mid, s, t, ans);
        if(mid + 1 <= t)
            query(rs, mid + 1, y, s, t, ans);
    }
  • 相关阅读:
    理解SetCapture、ReleaseCapture、GetCapture(控制了消息发往哪个窗口,是理解消息的关键)
    Javascript 的addEventListener()及attachEvent()区别分析
    鼠标拖拽
    鼠标右键菜单
    keydown
    一串跟随鼠标的DIV
    event对象和事件冒泡
    发表说说
    文档流
    CSS3 @keyframes 规则
  • 原文地址:https://www.cnblogs.com/khbcsu/p/3897831.html
Copyright © 2011-2022 走看看