定義:
權值線段樹,基於普通線段樹,但是不同。
舉個栗子:對於一個給定的數組,普通線段樹可以維護某個子數組中數的和,而權值線段樹可以維護某個區間內數組元素出現的次數。
在實現上,由於值域范圍通常較大,權值線段樹會采用離散化或動態開點的策略優化空間。單次操作時間復雜度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; }
進階知識 主席樹:https://www.cnblogs.com/young-children/p/11787490.html
參考博客:https://blog.csdn.net/qq_39565901/article/details/81782611