1. 程式人生 > >主席樹總結

主席樹總結

style ins -c 技術 基礎上 原來 middle lap spa

PS:待修改主席樹不會,樹套樹從來寫不動

其實很早就學過這個東西了,基本的思路也可以理解,最基本的應用就是用來求區間第K大

貼一下我的主席樹模板

技術分享圖片
#include<iostream>
#include<cstdio>
#include<algorithm>
#define M 200010
#define ls ch[node][0]
#define rs ch[node][1]
using namespace std;

int n,m,cnt,sz;
int a[M],b[M],rt[M],sum[M<<5],ch[M<<5
][2]; int get(int x) { int l=1,r=cnt; while(l<=r) { int mid=(l+r)/2; if(b[mid]==x) return mid; if(b[mid]>x) r=mid-1; else l=mid+1; } } void build(int &node,int l,int r) { node=++sz; if(l==r) return; int mid=(l+r)/2
; build(ls,l,mid),build(rs,mid+1,r); } void insert(int &node,int pre,int l,int r,int x) { node=++sz; sum[node]=sum[pre]+1,ls=ch[pre][0],rs=ch[pre][1]; if(l==r) return; int mid=(l+r)/2; if(x<=mid) insert(ls,ch[pre][0],l,mid,x); else insert(rs,ch[pre][1],mid+1
,r,x); } int query(int l1,int r1,int l,int r,int k) { if(l==r) return l; int now=sum[ch[l1][0]]-sum[ch[r1][0]],mid=(l+r)/2; if(k<=now) return query(ch[l1][0],ch[r1][0],l,mid,k); else return query(ch[l1][1],ch[r1][1],mid+1,r,k-now); } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); b[++cnt]=a[i]; } sort(b+1,b+1+n); cnt=unique(b+1,b+1+n)-b-1; build(rt[0],1,n); for(int i=1;i<=n;i++) { a[i]=get(a[i]); rt[i]=rt[i-1]; insert(rt[i],rt[i-1],1,cnt,a[i]); } for(int i=1;i<=m;i++) { int l,r,k;scanf("%d%d%d",&l,&r,&k); printf("%d\n",b[query(rt[r],rt[l-1],1,cnt,k)]); } return 0; }
View Code

一般來說理解了權值線段樹再學主席樹就很好理解了,因為權值線段樹存在可減性,所以最基礎的主席樹就是在兩棵權值線段樹每個節點相減得到的新的線段樹上進行操作,當然還會有別的變化,但無論怎麽變這個線段樹都應該是權值線段樹

最近碰到了這樣一個類似二維數點的題目然而我這麽菜當然不會做啦

有$n$條線段,每次查詢在區間[$l$,$r$]內線段的條數

首先我們把線段都按照左端點排好序,這樣問題就轉化成:

求所有左端點在區間[$l$,$r$]內,其對應的右端點也在區間[$l$,$r$]內的點對的個數

我們可以對每個左端點建立主席樹,維護其右端點的位置,查詢就是求$Tree[r]$-$Tree[l-1]$構成的權值線段樹在區間[$l$,$r$]內的$size$

是不是很簡單~

下面放幾道我最近做的主席樹的題目

1.Count on a tree

題意:求樹上兩點之間的點權第k大值

對於一條鏈$u->v$上的信息,我們可以把它看成$(u->root)+(v->root)-LCA->root-fa_LCA->root$。這樣我們把每個點在它的父節點的基礎上建立主席樹,查詢的時候就不再是兩棵樹相減,而是四棵樹相加減了

2.middle

題意:給定一個序列,每次查詢左端點在[$a$,$b$]之間,右端點在[$c$,$d$]之間的中位數最大的子串

我們考慮轉化成二分和0/1序列判定,即我們應該知道的是二分到每個數時序列的0/1狀態,當我們加入最小的數時,序列的0/1狀態為該位置是0,其他為1,再加入第二個數時,又在第二個數的位置上多了一個0。排序後每個數的二分序列都是在上一個數的二分序列的基礎上增加一個0。那麽思路就很明顯了,用主席樹維護每次在原來的基礎上加修改一個位置即可。(因為這個主席樹跟上邊的不是很一樣,感覺說的不是很清楚,自己畫畫圖就很明顯了)

3.談笑風生

題意:給定一棵樹T,每次詢問給出兩個數$a$和$k$,求使得$b$和$a$距離不超過$k$,且$a,b$均為$c$的祖先的三元組的個數

首先我們分析題目可以發現,這個$c$求存在於$a,b$深度較大的那個點得子樹中

先考慮$deep_a > deep_b$,那麽答案即為$k*(size_a -1)$

否則,我們需要枚舉每個$a$的子樹內與$a$距離不超過$k$的點的$size-1$

那麽這玩意我們用主席樹維護一下即可,因為是子樹問題,所以主席樹應該按照DFS序去建立

4.mex

題意:有一個長度為n的數組{a1,a2,...,an}。m次詢問,每次詢問一個區間內最小沒有出現過的自然數。

一道思路很巧妙的主席樹的題目,也可以用莫隊做(但是我感覺莫隊的轉移復雜度不是很靠譜)

我們考慮一個一個數如果出現在[$l$,$r$]區間內,那麽在$r$之前,該數最晚出現的位置一定大於等於$l$,用主席樹維護一下每個數出現的最晚時間,對於一個大於n的數,是不可能到達的,直接忽略它就行了

主席樹總結