權值線段樹


定義:

權值線段樹,基於普通線段樹,但是不同。

舉個栗子:對於一個給定的數組,普通線段樹可以維護某個子數組中數的和,而權值線段樹可以維護某個區間內數組元素出現的次數。

在實現上,由於值域范圍通常較大,權值線段樹會采用離散化或動態開點的策略優化空間。單次操作時間復雜度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\n",minn);
  }
  return 0;
}
View Code

 

進階知識 主席樹:https://www.cnblogs.com/young-children/p/11787490.html

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM