美文网首页数据结构
解析·优化 ZKW线段树

解析·优化 ZKW线段树

作者: 影踪派熊猫人武僧 | 来源:发表于2018-10-30 20:37 被阅读151次

    德鲁伊!大自然已经听命于我了!
    死亡之翼长子奈法利安

    ZKW天下第一!
    摘自某群聊天记录

    ZKW线段树,即非递归形式的线段树,出自终极大犇ZKW的论文《统计的力量》。与普通的线段树相比,ZKW线段树由于是非递归形式,效率极高,代码也极短,成为了OI比赛中极为实用的优化算法之一。虽然ZKW线段树无法处理有运算优先级的线段树问题,但是在一般的问题上和常数偏大的问题上总能带来极强的游戏体验。

    ZKW线段树的建树

    普通线段树
           1
          / \
         2   3                     <---------------"弱小,可伶又无助"
        / \ 
       4   5
    
    ZKW线段树
             1
           /   \
         10     11                     <---------------"天下第一!"
        / \     / \
      100 101 110 111
    

    那么接下来进入我们的分析环节:小学生找规律
    通过观察,我们可以发现:线段树对应叶子节点的下标和原数组的下标的差值是恒定的
    事实上,这个值几乎恒等于线段树数组里叶子节点的数量。
    事实上,该值num满足:num=2^{[log_2(n+1)]}
    于是我们可以先将线段树建为一棵满二叉树,然后我们从叶子节点开始回溯即可。
    定义

    #define maxn 10000
    int n,num;
    int minv[maxn<<2];
    

    其中,minv为线段树数组,n为总节点数量,N即为上文提到的N。
    那么完整的建树代码如下

    inline int ksm(int x,int y){
        int res=1;
        while(y){
            if(y&1)res*=x%p;
            x*=x%p;
            y>>=1;
        }
        return res;
    }
    inline void build(){
      scanf("%d", &n);
      N=ksm(2,log2(n+1));
      for(register int i=N+1;i<=N+n;i++)cin>>minv[i];
      for(register int i=N-1;i>=1;i--)minv[i]=minv[i<<1]+minv[i<<1|1];
    }
    

    ZKW线段树的修改&查询

    单点修改与单点查询

    代码量很少,背模板即可

    单点更新
    inline void update(int x,int k){
        for(register int i=x+N;i;i>>=1)tree[i]+=k;
    }
    
    区间(单点)查询
    inline int query(int l,int r){
        int ans=0;
        for(l=N+l-1,r=N+r+1;s ^ r ^ 1;s>>=1,r>>=1){
            if(~s&1)ans+=tree[s ^ 1];
            if(r&1)ans+=tree[r ^ 1];
        }
        return ans;
    }
    
    ZKW线段树单点操作
    #include<bits/stdc++.h>
    #define maxn 10000
    using namespace std;
    int n,N;
    int a[maxn];
    int ansv[maxn<<2];
    inline int ksm(int x,int y){
        int res=1;
        while(y){
            if(y&1)res*=x%p;
            x*=x%p;
            y>>=1;
        }
        return res;
    }
    inline void build(int n){
      N=ksm(2,log2(n+1));
      for(register int i=N+1;i<=N+n;i++)ansv[i]=a[i];
      for(register int i=N-1;i>=1;i--)ansv[i]=ansv[i<<1]+ansv[i<<1|1];
    }
    inline void update(int x,int k){
        for(register int i=x+N;i;i>>=1)ansv[i]+=k;
    }
    inline int query(int l,int r){
        int ans=0;
        for(l=N+l-1,r=N+r+1;s ^ r ^ 1;s>>=1,r>>=1){
            if(~s&1)ans+=tree[s ^ 1];
            if(r&1)ans+=tree[r ^ 1];
        }
        return ans;
    }
    

    区间修改与区间查询

    与普通线段树类似的,我们在ZKW线段树上也不能使用暴力的方式进行区间修改。在ZKW线段树上做暴力修改的复杂度甚至比普通线段树更高。同时,在ZKW线段树中我们仍然需要使用到lazy标记。然而不同的是,在ZKW线段树中我们会将lazy标记永久固化,即永远不对标记做pushdown操作。
    我们假定现在指定了一个区间[l,r],使得区间的每一个值全部加上k,并使得l=2,r=10
    l到了[2,2]节点时,显然[3,3]节点需要被标记上k,那么接下来我们走到的[2,3]、[0,3]节点都会被标记上k*1,等我们到达[0,7]节点时,因为[4,7]已经被标记了k,所以[0,7]节点的值要加上k*(1+4)=k*5,自然我们需要一个变量来存储k的系数。
    需要注意的是,这次的修改要上推到根节点

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

    区间查询的过程类似。

    inline int query(int l, int r){
        int lNum=0,rNum=0,nNum=1;
        int ans=0;
        for(l=N+l-1,r=N+r+1;l ^ r ^ 1;l>>=1,r>>=1,nNum<<=1){
            if(lazy[l])ans+=lazy[l]*lNum;
            if(lazy[r])ans+=lazy[r]*rNum;
            if(~l&1){
                ans+=ansv[l ^ 1];
                lNum+=nNum;
            }
            if(r&1){
                ans+=ansv[r ^ 1];
                rNum+=nNum;
            }
        }
        for(;l;l>>=1,r>>=1) {
            ans+=lazy[l]*lNum;
            ans+=lazy[r]*rNum;
        }
        return ans;
    }
    
    线段树区间操作代码
    #include<bits/stdc++.h>
    #define maxn 10000
    using namespace std;
    int n,N;
    int a[maxn];
    int ansv[maxn<<2],lazy[maxn<<2];
    inline int ksm(int x,int y){
        int res=1;
        while(y){
            if(y&1)res*=x%p;
            x*=x%p;
            y>>=1;
        }
        return res;
    }
    inline void build(int n){
      N=ksm(2,log2(n+1));
      for(register int i=N+1;i<=N+n;i++)ansv[i]=a[i];
      for(register int i=N-1;i>=1;i--)ansv[i]=ansv[i<<1]+ansv[i<<1|1];
    }
    inline void update(int l,int r,int k) {
        int lNum=0,rNum=0,nNum=1;
        for(l=N+l-1,r=N+r+1;l ^ r ^ 1;l>>=1,r>>=1,nNum<<=1){
            ansv[l]+=k*lNum;
            ansv[r]+=k*rNum;
            if(~l&1){
                lazy[l ^ 1]+=k;
                ansv[l ^ 1]+=k*nNum;
                lNum += nNum;
            }
            if(r&1){
                lazy[r ^ 1]+=k;
                ansv[r ^ 1]+=k*nNum;
                rNum+=nNum;
            }
        }
        for(;l;l>>=1,r>>=1){
            ansv[l]+=k*lNum;
            ansv[r]+=k*rNum;
        }    
    }
    inline int query(int l, int r){
        int lNum=0,rNum=0,nNum=1;
        int ans=0;
        for(l=N+l-1,r=N+r+1;l ^ r ^ 1;l>>=1,r>>=1,nNum<<=1){
            if(lazy[l])ans+=lazy[l]*lNum;
            if(lazy[r])ans+=lazy[r]*rNum;
            if(~l&1){
                ans+=ansv[l ^ 1];
                lNum+=nNum;
            }
            if(r&1){
                ans+=ansv[r ^ 1];
                rNum+=nNum;
            }
        }
        for(;l;l>>=1,r>>=1) {
            ans+=lazy[l]*lNum;
            ans+=lazy[r]*rNum;
        }
        return ans;
    }
    

    相关文章

      网友评论

        本文标题:解析·优化 ZKW线段树

        本文链接:https://www.haomeiwen.com/subject/htmitqtx.html