區間第K值 主席樹詳解

2021-08-07 19:00:38 字數 3000 閱讀 1041

序:這是一篇遲到的題解,機房的小夥伴們系統地學主席樹應該是七月份的時候,然而我沒趕上趟,當時壓根沒看懂主席樹是什麼東東。 昨天晚上決定重新來過,於是請教了一位大神1113(這是他的部落格,不過好像因為手機驗證的原因很久沒有更新了),他告訴我了主席樹的始末,然後我就秒懂了,原來並沒有想象中的那麼複雜,相信看完了這篇題解,你也會這麼覺得的。下面開始正文:

給定乙個長度為

n 的序列和

m個詢問,對於詢問,輸出區間[l

,r] 第

k 大的數。

對於乙個問題,我們一般採取的方法就是化繁為簡,這樣更容易分析題目的特點,然後逐個擊破。對於問題中的

m個詢問,我們可以先看看如何解決1個詢問。

對於乙個詢問(l

,r,k

) ,剛開始最容易想到的是二分答案,但是ch

eck 的時候又不得不o(

n)地掃一遍區間,總的複雜度就變成o(

nlog

n),這顯然不是很優。我們完全可以o(

n)地求出第

k 大的值,我們可以先用計數的方法記下區間中每種數的個數(在這之前要先把整個陣列離散化),然後從大到小維護乙個cn

t的字尾和,表示大於或等於當前數字的數的個數,當這個字尾和大於或等於

k 時,我們也就找到了這個區間內第

k大的數了。省去離散的部分,**如下:

void solve()

}}

上面方法的複雜度是o(

n)的,假如用來解決

m 個詢問的話總複雜度是o(

n∗m)

,這樣只能過70%的資料,那麼能不能再優化一下呢?這裡就要用到主席樹了。

對於上面一次詢問o(

n)的演算法, 我們在兩個地方用到了o(

n),乙個是在計數的時候,乙個是在維護字尾和的時候。這裡先講怎麼優化維護字尾和的演算法,也就是假設我們已經計好區間內的數了,我們怎麼更快地找到第

k 大數呢,這裡只要維護乙個線段樹就行了,我們讓線段樹以離散後的權值為下標,然後在節點存下當前區間的數的個數su

m,只要寫乙個fi

nd函式來從右往左找到第

k 個數即可。每次先詢問右區間,假如右區間的su

m≥k,那麼就繼續在右區間從右往左找第

k 個數;假如右區間的su

m<

k,那麼就要在左區間從右往左找第k−

sum 個數,直到

l =

r。下面給出**:

struct segment_treetree[n<<2];

int find(int k,int p)

}t;

雖然說查詢只有o(

logn

) ,但是我們建樹要o(

nlog

n)啊!?不是的,我們並不是對於每次詢問都建乙個線段樹,而是在詢問前就已經建好了,這就是主席樹。下面講講如何建樹:我們最初要先bu

ild 一棵空線段樹,並且要動態開節點,而不是像之前的線段樹一樣用p∗

2和p∗

2+1 表示左右兒子。然後我們從左往右遍歷整個陣列,每遍歷乙個數就在之前的樹的基礎之上再建一棵樹,然而對於乙個數,他從根節點do

wn到葉子節點最多隻會經過lo

gn個節點,所以其他節點的資訊都不會改變,我們只要再多開lo

gn個新節點,再加上原來的節點就可以表示出當前的線段樹了,這就是動態開節點的妙處。我們用ro

ot[i

] 表示第

i 棵線段樹的根節點編號,第

i線段樹就表示已經插入了[1

,i] 區間內節點的線段樹,這就相當於是一棵字首線段樹,節點記憶體了[1

,i] 區間內各種數字的個數。查詢[l

,r] 區間時,只要把第

r 棵線段樹的su

m減去第l−

1 棵線段樹的su

m ,就可以得到[l

,r] 區間的su

m 了。時間和空間複雜度都是o(

nlog

n)的,於是問題就迎刃而解了。

#include

#include

#include

#define n 30005

#define m 30005

using

namespace

std;

template

inline

void rd(t &res)

dowhile(c=getchar(),c>=48);

res*=k;

}template

inline

void pt(t res)

if(res>=10)pt(res/10);

putchar(res%10+48);

}struct oprq[m];

int n,m,len;

int a[n],tmp[n];

struct segment_treetree[n*15];

int tot,root[n];//tot用來動態開節點,root存第i棵字首線段樹的節點編號

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

void insert(int t,int l,int r,int x,int &p)

int find(int t,int l,int r,int k,int p)

segment_tree()

}t;void solve()

}int main()

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

//離散

sort(tmp+1,tmp+n+1);

len=unique(tmp+1,tmp+n+1)-tmp-1;

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

a[i]=lower_bound(tmp+1,tmp+len+1,a[i])-tmp;

solve();

return

0;}

區間第k大(主席樹)

學了一下主席樹模板題,當初看了網上的主席樹講解都沒有看懂,後面看了嗶哩嗶哩的uestc的主席樹,終於看懂了思想。每次更新的複雜度都為logn。每次更新的話就是對要更新的點路徑上的點重新更加乙個,然後進行對沒有影響的那些進行連邊。然後用乙個root記錄每乙個線段樹的根節點下標。include incl...

主席樹(區間第k小)

k th number 求區間內第k小的數。主席樹的板子題 主席樹左子樹存小值,右邊大值,用sum記錄一下子樹節點個數。對 l,r 的查詢區間,root r root l 1 可得出 l,r 的差值,也就是大小的個數 include include include include include i...

主席樹 區間第k小

主席樹 權值線段樹 可持久化 權值線段樹 在此處指各個數字在某個區間內出現的次數 那麼第一棵權值線段樹會記錄 1,1 的數字出現次數 第n棵權值線段樹會記錄 1,n 的數字出現次數 例 數列為110001 第一棵權值線段樹記錄為tree1 0 0 tree1 1 1 第二棵權值線段樹記錄為tree2...