zoukankan      html  css  js  c++  java
  • 『zkw线段树』zkw线段树略讲

    zkw线段树学习前提须知

    该线段树几乎可以处理线段树的所有问题,比线段树的速度快很多,但比树状数组的速度慢,而且,代码超短!

    zkw线段树不能处理 有运算优先级的问题 ,可以说吊打线段树

    算法内容

    zkw线段树略讲

    详细可以参考 洛谷日报 讲的很棒,但是图中没有二进制图,要二进制图可以参考 有二进制图的zkw线段树

    zkw线段树相对于原本的线段树不同的是,它是一棵完全二叉树,是真正的要开满四倍空间的线段树,该线段树是将所有节点转化为了二进制的形式,然后对于每个叶子节点,我们把值存进去,听起来感觉和线段树差不多,那为什么快呢?原理你可以看上面两个博客,我这里对代码进行分析

    【单点修改 + 区间查询】

    首先是 建树

    //#define fre yes
    
    #include <cstdio>
    
    int N = 1;
    
    inline void build(int n) {
        for (; N <= n + 1; N <<= 1);
        for (int i = N + 1; i <= N + n; i++) read(tree[i]);
        
        for (int i = N - 1; i >= 1; i--) tree[i] = tree[i << 1] + tree[i << 1 | 1];
    }
    

    先来解决几个疑问,N是个什么东西?我们的zkw线段树是一个完全二叉树,而我们也是对一个完全二叉树的叶子节点填数的,那我们填数一定是在叶子节点上进行填数,我们都知道对应完全二叉树上的叶子节点的编号,是原数组编号加个固定的常数(可以自己试试?) 那这个常数是怎么计算呢?通过下面的式子

    [N = 2^left lceil log_2^{n + 1} ight ceil ]

    那这样我们就能得到这个N,于是上面的式子是怎么出来的就很显然了。

    接下来说怎么搞这个单点修改

    inline void modify(int x, int k) {
        for(x += N; x; x >= 1) tree[x] += k;
    }
    

    好了,我觉得也不用解释了,很简单..

    接下来是区间查询

    单点修改的区间查询相对于简单很多

    int query(int s, int t) {
        int ans = 0;
        for (s = N + s - 1, t = N + t - 1; s ^ t ^ 1; s >>= 1, t >>= 1) {
           	if(~s & 1) ans += tree[s ^ 1];
            if(t & 1) ans += tree[t ^ 1];
        } return ans;
    }
    

    我们的区间查询遵守一个规定就是

    1、s指向的节点是左儿子,那么ans += 右儿子的值

    2、t指向的节点是右儿子,那么ans += 左儿子的值

    (看图我们知道 左儿子最后一位为0,右儿子最后一位为1)

    这里右儿子,左儿子都是针对同一深度的两个儿子而言的(这里是关键) 不然不好理解上面的代码

    这里用别人博客的图片对着看一下吧,文字和其他的都不用管 就关注一下每个节点的二进制

    ~s是指取反 10011 -> 01100

    然而我们发现我们只能查询[1, n-1]

    如果想查询[0, m] 我们就将数组整体平移

    如果想要查询[m, n] 直接将N扩大两倍

    【区间修改,区间查询】

    首先是建树(和上面一样 这里略)

    区间修改

    这里需要用到 标记永久化 的思想,就是不下推Lazy标记,让其一直存在

    含义和区间修改的线段树差不多,这里就不举例子了,直接看代码吧

    inline void update(int s, int t, int k) {
        int lNum = 0, rNum = 0, nNum = 1;
        for (s = N + s - 1, t = N + t + 1; s ^ t ^ 1; s >>= 1, t >>= 1, nNum <<= 1) {
            tree[s] += k * lNum;
            tree[t] += k * rNum;
            if(~s & 1) {
                add[s ^ 1] += k;
                tree[s ^ 1] += k * nNum;
                lNum += nNum;
            }
            
            if(t & 1) {
                add[t ^ 1] += k;
                tree[t ^ 1] += k * nNum;
                rNum += nNum;
            }
        }
        
        for (; s; s >>= 1, t >>= 1) {
            tree[s] += k * lNum;
            tree[t] += k * rNum;
        }
    }
    

    哎,这个区间修改我知道大家都有很问题,我们一个一个来解决

    首先是这个 lNum, rNum, nNum 分别是什么意思

    lNum表示的意思是,从s一路走来已经包含了几个数

    rNum表示的意思是,从t一路走来已经包含了几个数

    nNum表示的意思是,本层中每个节点包含几个数(就是一个深度的所有父亲节点所包含的儿子数目)

    第一个转移 tree[s] += k ( imes) lNum 和 tree[t] += k ( imes) rNum 是什么意思呢?

    很明显,每次我们都是倒着处理的,那么这个转移就是很明显了,可以类比线段树

    第二个转移 两个if里面的式子变了,我们来一个一个分析

    首先是add,这很显然,我们每次要把之前的指针往上传,但是原本的不变

    然后是tree,这也很显然,和第一个转移一样

    然后是rNum 和 lNum,这个转移又是什么意思呢,我们知道我们只会遍历到一个路径上,那么其中肯定有一个父亲节点的另外一棵子树不会被遍历到,但是实际上这些点也是在我们的修改范围内的,所以我们这里要这么统计,方便我们对rNum, lNum进行操作

    最后我们再将两个点走到根节点就好了

    最后是区间查询

    和上面差不多,这里就放代码辣

    int query(int s, int t) {
        int lNum = 0, rNum = 0, nNum = 1;
        int ans = 0;
        for (s = N + s - 1, t = N + t + 1; s ^ t ^ 1; s >>= 1, t >>= 1, nNum <<= 1) {
            if(add[s]) ans += add[s] * lNum;
            if(add[t]) ans += add[t] * rNum;
            if(~s & 1) {
                ans += tree[s ^ 1];
                lNum += nNum;
            }
            
            if(t & 1) {
                ans += tree[t ^ 1];
                rNum += nNum;
            }
        }
        
        for (; s;s >>= 1, t >>= 1) {
            ans += add[s] * lNum;
            ans += add[t] * rNum;
        } return ans;
    }
    

    是不是和上面很像,原理都是一样的 可以类比线段树区间查询

    上面仅仅是举了一个区间求值的例子,当然也可以引申到求最大最小值上

  • 相关阅读:
    spring mvc 建立下拉框并进行验证demo
    spring mvc 使用jsr-303进行表单验证的方法介绍
    复习
    练习
    复习
    计算机网络
    liunx 和网络
    按钮
    几天不见 就记得这个架子了。。。。
    只能输入汉字登录页面
  • 原文地址:https://www.cnblogs.com/Nicoppa/p/11650015.html
Copyright © 2011-2022 走看看