主席樹學習筆記

2022-02-24 21:32:48 字數 3276 閱讀 4618

學習博文:主席樹總結

p3834 【模板】可持久化線段樹 2(主席樹)

給出乙個序列,每次詢問給定區間內第k小的值。

主席樹模板。

考慮最簡單的情況,也就是查詢區間固定。首先對資料進行離散化,用線段樹維護。每個節點對應離散化後值域的數的總個數 size.從上到下進行查詢時,判斷當前節點左子樹的 \(size\) 和排名 \(k\) 的關係,如果是小於等於就到左子樹裡面去,否則到右子樹中查詢 \(k-size\) (這個原理參考平衡樹的kth)。

如何維護所有區間?最直接的想法就是建 \(n\) 個線段樹,維護 \([i,i]\) 的區間情況,利用字首和實現所有區間。但空間肯定會炸。

考慮可持久化線段樹是如何解決空間問題的。顯然,從區間 \([1,i-1]\) 到 \([1,i]\) 只是改變了乙個值,那麼同樣的,每增加乙個區間只需要新開 \(logn\) 個節點即可。

**如下。

//p3834 【模板】可持久化線段樹 2(主席樹)

//每一棵線段樹維護乙個區間的最值,然後按照可持久化的思想,每一棵新的樹增加log個節點。

#include using namespace std;

const int n=2e5+10;

struct node

tr[n<<5];

int a[n],rt[n],n,m,tot=0;

vectorv;

int getid( int k )

void build( int &trt,int l,int r )

void update( int l,int r,int &now,int las,int k )

int query( int l,int r,int x,int y,int k )

int main()

}

p2617 dynamic rankings

給定乙個含有 \(n\) 個數的序列 \(a_1,a_2 \dots a_n\) ,需要支援兩種操作:

對於修改操作,設位置為 \(i\),從下標為 \(i\) 的樹狀陣列節點開始,每次往後跳,所有跳到的線段樹都改一遍,原值對應區間-1,新值對應區間+1。一共要改 \(log\) 棵樹。

對於查詢操作,先把 \(l−1\) 和 \(r\) 都往前跳,每次跳到的都記下來。求當前 \(size\) 的時候,用記下來的 \(log\) 棵由 \(r\) 得到的節點左兒子的 \(size\) 和(就代表 \([1,r]\) 的 \(size\) )減去 \(log\) 棵由 \(l−1\) 得到的節點左兒子的 \(size\) 和(就代表 \([1,l−1]\) 的\(size\) )就是 \([l,r]\) 的 \(size\) 。往左/右兒子跳的時候也是 \(log\) 個節點一起跳。

#include using namespace std;

const int n=1e5+10;

struct segmenttree

tr[n*400];

struct question

q[n];

int n,m,a[n],rt[n],len,tot,tmp[2][20],cnt[2],num[n<<1];

char opt[10];

int lowbit( int x )

void modify( int &p,int l,int r,int pos,int val )

void init_modify( int x,int val )

int query( int l,int r,int k )

else

}int init_query( int l,int r,int k )

int main()

//printf( "input has done." );

sort( num+1,num+1+len ); len=unique( num+1,num+1+len )-num-1;

for ( int i=1; i<=n; i++ )

init_modify( i,1 );

for ( int i=1; i<=m; i++ )

if ( q[i].typ ) printf( "%d\n",num[init_query(q[i].l,q[i].r,q[i].k)] );

else

}

p2633 count on a tree

給定一棵 \(n\) 個節點的樹,每個點有乙個權值。有 \(m\) 個詢問,每次給你 \(u,v,k\) ,你需要回答 \(u \text\) 和 \(v\) 這兩個節點間第 \(k\) 小的點權。其中 \(\text\) 是上乙個詢問的答案,定義其初始為 \(0\) ,即第乙個詢問的 \(u\) 是明文。

顯然,首先可以樹上差分維護每個點到根的字首和。

詢問 \(u,v\) 的時候,可以知道 \(siz[rt,u]\) 和 \(siz[rt,v]\) 的和。那麼,用 \(siz[rt,u]+siz[rt,v]-siz[rt,lca]-siz[rt,fa[lca]]\) ,四個點一起跳。每個點對應的線段樹從其父親的線段樹繼承而來(根節點從 \(0\) 號空線段樹繼承而來),這兩個操作在 dfs 建樹時就可以一併處理。

#include using namespace std;

const int n=1e5+10,m=2e6+10;

struct edge

e[n<<1];

int n,m,s,lasans=0,tot,cnt,head[n];

int a[n],tmp[n],fa[n][35],dep[n],rt[m]=,ls[m]=,rs[m]=,siz[m]=;

void add( int u,int v )

; head[u]=tot;

}void modify( int &rt,int las,int l,int r,int val )

int mid=(l+r)>>1;

if ( mid>=val ) modify( ls[rt],ls[las],l,mid,val ),rs[rt]=rs[las];

else modify( rs[rt],rs[las],mid+1,r,val ),ls[rt]=ls[las];

siz[rt]=siz[ls[rt]]+siz[rs[rt]];

}int query( int rt1,int rt2,int rt3,int rt4,int l,int r,int k )

void dfs( int u,int fat )

}int lca( int x,int y )

int main()

}

主席樹學習筆記

問題 給定乙個n個數的序列,q次詢問第x個數到第y個數中的第k最值。我們假定是第k小。為了使討論更加簡便,我們假定序列的每個數都是不大於n的正整數。當然一般題目中元素範圍很大,但是可以用離散化預處理來做到這一點。考慮乙個比較高階的做法 令g i j 為前i個數中,值為j的數的個數。很容易用o n 2...

主席樹 學習筆記

主席樹就是權值線段樹的乙個集合 模板題為求某個區間的第 k 大,權值線段樹也有求第 k 大這個功能,但是不能維護區間,只能求整個全域性第 k 大 所以學習這個之前,務必先搞懂權值線段樹 所以 都是這道題的 模板 可持久化線段樹 1 主席樹 1 先從建樹開始說起 int build int l,int...

主席樹 學習筆記

對於詢問 1,n 的第 k 小數,我們都知道直接上權值線段樹就行了。那麼對於任意區間的第 k 小數呢?暴力一點,每次開一顆線段樹。空間肯定 那麼此時,主席樹便應運而生。主席樹的主要思想就是 保留每次插入操作時的歷史版本,以便查詢區間第 k 小的數。先說流程。1.先建一顆空的權值線段樹,1,len 2...