序:這是一篇遲到的題解,機房的小夥伴們系統地學主席樹應該是七月份的時候,然而我沒趕上趟,當時壓根沒看懂主席樹是什麼東東。 昨天晚上決定重新來過,於是請教了一位大神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...