zoukankan      html  css  js  c++  java
  • 权值线段树详解

    简述

      权值线段树是线段树的一种,可以实现平衡树的基本操作,权值线段树的节点维护的是这个区间的数的出现次数,例如区间序列为1 2 2 3 3 3,那么该节点的值为6。权值线段是可以在logn的时间内求出全局的k小值,某数在全局的rank,一个数的前驱或者后继。因为是线段树的一种所以思想也是二分,其本质就是一个可以二分的桶。

    代码详解

    定义

    因为是线段树的一种所以空间也开4n

    typedef long long ll;
    ll a[maxn<<2];

    单点更新

    单点更新的操作和普通线段树一样,递归到某个叶子节点操作即可

    void update(int pos,int val,int l,int r,int rt){
        if(l==r){
            a[rt]+=val;
            return;
        }
        int m=l+r>>1;
        if(pos<=m)
            update(pos,val,l,m,rt<<1);
        else
            update(pos,val,m+1,r,rt<<1|1);
        pushup(rt);
    }

    第k小的值

    这里利用了线段树的天然二分性,如果左子树的值>=k也就是说k小值在左子树,那么就往左子树递归,否则就往右子树递归,比如查第5小,左子树只有三个,那么就往右子树递归查询第2小

    ll kth(int k,int l,int r,int rt){
        //查询第k小的那个数 
        if(l==r){
            return l;
        }
        int m=l+r>>1;
        if(a[rt<<1]>=k)    return kth(k,l,m,rt<<1);
        else return kth(k-a[rt<<1],m+1,r,rt<<1|1);
    }

    求一个数在全局的排名

    排名数值上等于全局比那个数小的数的个数+1,那么放在权值线段树里就等价于前缀和查询,因为节点维护的是区间内出现的数的次数,例如我们求x的排名我们只需要查询[1,x-1]这个区间内的前缀和,然后加1就是x的排名了。

    那也就是普通线段树的区间查询了,只不过左区间恒为1罢了(surprise!)

    ll query(int L,int R,int l,int r,int rt){
        //普通的区间查询 
        if(L<=l&&r<=R){
            return a[rt];
        }
        int m=l+r>>1;
        ll ans=0;
        if(L<=m)
            ans+=query(L,R,l,m,rt<<1);
        if(R>m)
            ans+=query(L,R,m+1,r,rt<<1|1);
        return ans;
    }
    ll frank(ll x){
        if(x==1) return 1;
        return query(1,x-1,1,n,1)+1;
    }

    前驱

    前驱就是小于这个数(设为p)的最大的数,所以我们优先查小于x但尽量大的数,所以当右子树有小于p的时候我们优先递归右子树,直到整个区间小于p,那该区间的右端点就是我们要求的前驱了。所以我们分两部,找到整个区间刚好小于p的节点,然后查询那个节点的非空最右子树。

    ll findpre(int l,int r,int rt){
        //查询节点rt的非空最右子树 
        if(l==r) return l;
        int m=l+r>>1;
        if(a[rt<<1|1]) return findpre(m+1,r,rt<<1|1);
        return findpre(l,m,rt<<1);
    } 
    ll pre(int p,int l,int r,int rt){
        if(r<p){
            if(a[rt]) return findpre(l,r,rt);    
            return 0;
        }
        int m=l+r>>1;
        int re; 
        if(m+1<p&&a[rt<<1|1]&&(re=pre(p,m+1,r,rt<<1|1))){                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
            return re;
        }
        return pre(p,l,m,rt<<1);
    }

    对于小于p的最大的数,我们设p的排名为rp,则p的前驱就是第rp-1小的数,所以我们可以通过frank和kth结合起来求前驱

    ll pre(int p){
        return kth(frank(p)-1,1,n,1);
    }

    后继

    后继就是找大于数p的最小数,思路和找前驱一样,分两步先找刚好整个区间大于p的节点,然后找节点的最左非空子树。

    ll findnext(int l,int r,int rt){
        //查询节点rt的最左非空子树 
        if(l==r){
            return l;
        }
        int m=l+r>>1;
        if(a[rt<<1]) return findnext(l,m,rt<<1);
        return findnext(m+1,r,rt<<1|1);
    }
    ll next(int p,int l,int r,int rt){
        if(p<l){
            if(a[rt]) return findnext(l,r,rt);
            return 0;
        }
        int m=l+r>>1;
        int re;
        if(m>p&&a[rt<<1]&&(re=next(p,l,m,rt<<1))){
            return re;
        }
        return next(p,m+1,r,rt<<1|1);
    }

    对于p的后继也一样,设p+1的排名为rp,那么p的后继就是第rp小的数

    ll next(int p){
        return kth(frank(p+1),1,n,1);
    }

    复杂度分析

    模板

    #include<iostream>
    #include<algorithm>
    using namespace std;
    typedef long long ll;
    const int maxn=
    int a[maxn];
    long long ans;
    void pushup(int rt){
        a[rt]=a[rt<<1]+a[rt<<1|1];
    }
    void update(int pos,int val,int l,int r,int rt){
        if(l==r){
            a[rt]+=val;
            return;
        }
        int m=l+r>>1;
        if(pos<=m)
            update(pos,val,l,m,rt<<1);
        else
            update(pos,val,m+1,r,rt<<1|1);
        pushup(rt);
    }
    ll kth(int k,int l,int r,int rt){
        //查询第k小的那个数 
        if(l==r){
            return l;
        }
        int m=l+r>>1;
        if(a[rt<<1]>=k)    return kth(k,l,m,rt<<1);
        else return kth(k-a[rt<<1],m+1,r,rt<<1|1);
    }
    ll query(int L,int R,int l,int r,int rt){
        //普通的区间查询 
        if(L<=l&&r<=R){
            return a[rt];
        }
        int m=l+r>>1;
        ll ans=0;
        if(L<=m)
            ans+=query(L,R,l,m,rt<<1);
        if(R>m)
            ans+=query(L,R,m+1,r,rt<<1|1);
        return ans;
    }
    ll frank(ll x){
        if(x==1) return 1;
        ll ans;
        return query(1,x-1,1,n,1)+1;
    }
    ll findpre(int l,int r,int rt){
        //查询节点rt的非空最右子树 
        if(l==r) return l;
        int m=l+r>>1;
        if(a[rt<<1|1]) return findpre(m+1,r,rt<<1|1);
        return findpre(l,m,rt<<1);
    } 
    ll pre(int p,int l,int r,int rt){
        if(r<p){
            if(a[rt]) return findpre(l,r,rt);    
            return 0;
        }
        int m=l+r>>1;
        int re; 
        if(m+1<p&&a[rt<<1|1]&&(re=pre(p,m+1,r,rt<<1|1))){                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
            return re;
        }
        return pre(p,l,m,rt<<1);
    }
    ll findnext(int l,int r,int rt){
        //查询节点rt的最左非空子树 
        if(l==r){
            return l;
        }
        int m=l+r>>1;
        if(a[rt<<1]) return findnext(l,m,rt<<1);
        return findnext(m+1,r,rt<<1|1);
    }
    ll next(int p,int l,int r,int rt){
        if(p<l){
            if(a[rt]) return findnext(l,r,rt);
            return 0;
        }
        int m=l+r>>1;
        int re;
        if(m>p&&a[rt<<1]&&(re=next(p,l,m,rt<<1))){
            return re;
        }
        return next(p,m+1,r,rt<<1|1);
    }
    View Code
  • 相关阅读:
    html——meta标签、link标签
    html——a标签中target属性
    html——相对路径、绝对路径(有待补充....)
    CSS——display:flex
    JS——AJAX
    JS——锚点的运用
    Json——转义符
    C#——工厂模式
    C#——反射动态创建类的实例
    “微信跳一跳”辅助脚本的一种简单实现
  • 原文地址:https://www.cnblogs.com/qq2210446939/p/12584797.html
Copyright © 2011-2022 走看看