權值線段樹

定義:html

權值線段樹,基於普通線段樹,可是不一樣。ios

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

在實現上,因爲值域範圍一般較大,權值線段樹會採用離散化或動態開點的策略優化空間。單次操做時間複雜度o(lognide

權值線段樹的節點用來表示一個區間的數出現的次數  例如: 數 1和2 分別出現3次和5次,則節點1記錄 3,節點2 記錄5, 1和2的父節點記錄它們的和8 .優化

存儲結構spa

堆式存儲:rt ,l, r,      rt<<! , l, m     rt<<1|1  ,m+1, r .net

結點式存儲 struct Node { int sum ,l , r :};code

基本做用:htm

查詢第k小或第k大。blog

查詢某個數排名。

查詢整幹數組的排序。

查詢前驅和後繼(比某個數小的最大值,比某個數大的最小值)

基本操做:

單點修改 (單個數出現的次數+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

 

進階知識 主席樹:http://www.javashuo.com/article/p-wqmknicq-bc.html

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

相關文章
相關標籤/搜索