• 权值线段树


    定义:

    权值线段树,基于普通线段树,但是不同。

    举个栗子:对于一个给定的数组,普通线段树可以维护某个子数组中数的和,而权值线段树可以维护某个区间内数组元素出现的次数。

    在实现上,由于值域范围通常较大,权值线段树会采用离散化或动态开点的策略优化空间。单次操作时间复杂度o(logn

    权值线段树的节点用来表示一个区间的数出现的次数  例如: 数 1和2 分别出现3次和5次,则节点1记录 3,节点2 记录5, 1和2的父节点记录它们的和8 .

    存储结构

    堆式存储:rt ,l, r,      rt<<! , l, m     rt<<1|1  ,m+1, r 

    结点式存储 struct Node { int sum ,l , r :};

    基本作用:

    查询第k小或第k大。

    查询某个数排名。

    查询整干数组的排序。

    查询前驱和后继(比某个数小的最大值,比某个数大的最小值)

    基本操作:

    单点修改 (单个数出现的次数+1)

        void update(int l,int r,int rt,int pos) // 当前区间范围 l r 节点 rt    位置 pos 
        {
            if(l==r) t[rt]++;
            else
            {
                int mid=(l+r)/2;
                if(pos<=mid) add(l,mid,rt*2,pos); else add(mid+1,r,rt*2+1,pos);
                t[rt]=t[rt*2]+t[rt*2+1];
            {
        }

    查询一个数出现的次数

        int query(int l,int r,int rt,int pos)
        {
            if(l==r) return t[rt];
            else
            {
                int mid=(l+r)/2;
                if(pos<=mid) return find(l,mid,rt*2,pos); else return find(mid+1,r,rt*2+1,pos);
            }
        }

    查询一段区间数出现的次数 查询区间 【x,y]

    递归+二分

        int query(int l,int r,int rt,int x,int y)
        {
            if(l==x&&r==y) return t[rt];
            else
            {
                int mid=(l+r)/2;
                if(y<=mid) return find(l,mid,rt*2,x,y);
                else if(x>mid) return find(mid+1,r,rt*2+1,x,y);
                else return find(l,mid,rt*2,x,mid)+find(mid+1,r,rt*2+1,mid+1,y);
            }
        }

    查询所有数的第k大值
    这是权值线段树的核心,思想如下:
    到每个节点时,如果右子树的总和大于等于k kk,说明第k kk大值出现在右子树中,则递归进右子树;否则说明此时的第k kk大值在左子树中,则递归进左子树,注意:此时要将k kk的值减去右子树的总和。
    为什么要减去?
    如果我们要找的是第7 77大值,右子树总和为4 44,7−4=3 7-4=37−4=3,说明在该节点的第7 77大值在左子树中是第3 33大值。
    最后一直递归到只有一个数时,那个数就是答案。

        int kth(int l,int r,int rt,int k)
        {
            if(l==r) return l;
            else
            {
                int mid=(l+r)/2,s1=f[rt*2],s2=f[rt*2+1];
                if(k<=s2) return kth(mid+1,r,rt*2+1,k); else return kth(l,mid,rt*2,k-s2);
            }
        }

    模板题:

    HDU – 1394

    给你一个序列,你可以循环左移,问最小的逆序对是多少???

    逆序对其实是寻找比这个数小的数字有多少个,这个问题其实正是权值线段树所要解决的

    我们把权值线段树的单点作为1-N的数中每个数出现的次数,并维护区间和,然后从1-N的数,在每个位置,查询比这个数小的数字的个数,这就是当前位置的逆序对,然后把当前位置数的出现的次数+1,就能得到答案。

    然后我们考虑循环右移。我们每次循环右移,相当于把序列最左边的数字给放到最右边,而位于序列最左边的数字,它对答案的功效仅仅是这个数字大小a[i]-1,因为比这个数字小的数字全部都在它的后面,并且这个数字放到最后了,它对答案的贡献是N-a[i],因为比这个数字大数字全部都在这个数字的前面,所以每当左移一位,对答案的贡献其实就是

    Ans=Ans-(a[i]-1)+n-a[i]

    由于数字从0开始,我们建树从1开始,我们把所有数字+1即可

    #include<iostream>
    #include<string.h>
    #include<algorithm>
    #include<stdio.h>
    using namespace std;
    const int maxx = 5005;
    int tree[maxx<<2];
    inline int L(int root){return root<<1;};
    inline int R(int root){return root<<1|1;};
    inline int MID(int l,int r){return (l+r)>>1;};
    int a[maxx];
    void update(int root,int l,int r,int pos){
       if (l==r){
         tree[root]++;
         return;
       }
       int mid=MID(l,r);
       if (pos<=mid){
          update(L(root),l,mid,pos);
       }else {
          update(R(root),mid+1,r,pos);
       }
       tree[root]=tree[L(root)]+tree[R(root)];
    }
    int query(int root,int l,int r,int ql,int qr){
         if (ql<=l && r<=qr){
            return tree[root];
         }
         int mid=MID(l,r);
         if (qr<=mid){
            return query(L(root),l,mid,ql,qr);
         }else if (ql>mid){
            return query(R(root),mid+1,r,ql,qr);
         }else {
            return query(L(root),l,mid,ql,qr)+query(R(root),mid+1,r,ql,qr);
         }
    }
    int main(){
      int n;
      while(~scanf("%d",&n)){
        int ans=0;
        memset(tree,0,sizeof(tree));
        for (int i=1;i<=n;i++){
            scanf("%d",&a[i]);
            a[i]++;
            ans+=query(1,1,n,a[i],n);
            update(1,1,n,a[i]);
        }
        int minn=ans;
        for (int i=1;i<=n;i++){
          ans=ans+(n-a[i]+1)-a[i];
          minn=min(ans,minn);
        }
        printf("%d
    ",minn);
      }
      return 0;
    }
    View Code

    进阶知识 主席树:https://www.cnblogs.com/young-children/p/11787490.html

    参考博客:https://blog.csdn.net/qq_39565901/article/details/81782611

  • 相关阅读:
    mongoDB BI 分析利器
    如何定位 Node.js 的内存泄漏
    如何对MySQL数据库中的数据进行实时同步
    [有奖活动进行中]阿里数据库专家曾文旌为你解决数据库那些事
    如何实现Docker应用的自定义弹性伸缩
    vue methods 中方法的相互调用
    vue 实现部分路由组件缓存
    SSH框架之一详解maven搭建多模块项目
    GitHub Pages 搭建流程-基于jekyll-bootstrap
    ExtJs 4 中的MVC应用架构
  • 原文地址:https://www.cnblogs.com/young-children/p/11787493.html
Copyright © 2020-2023  润新知