學習筆記 區間第k小演算法

2022-03-17 01:57:52 字數 2999 閱讀 9676

目錄靜態字首第k小

靜態區間第k小

動態區間第k小

前置知識:值域線段樹,可持久化線段樹,樹狀陣列

題目:給定乙個序列和m次操作,每次操作修改單點或者詢問整個序列第k小的數

首先考慮暴力,對於每次修改都直接排序的話,複雜度為o(nmlogn),也可以魔改一下排序方法,不過一般的暴力還是沒辦法過

整體第k小帶修改很明顯可以用平衡樹做,不過程式設計較麻煩(而且大材小用),所以不考慮

值域線段樹

值域線段樹可以很方便的o(logn)查詢一次所有數中比某個值小的數的個數,於是我們可以考慮用它解決這一類問題,當然一般來說值域線段樹是和離散化配套使用的

做法:將所有數離散化後加入值域線段樹,修改操作就直接刪除舊的,加入新的

對於查詢操作,從根節點開始,當前節點的左兒子儲存著\(≤\)mid的數的個數sum,如果sum\(≥\)k,就說明第k小應該在左邊,遞迴到左兒子,否則k-=sum,遞迴給右兒子(k-=sum是因為在整個區間找第k小等價於在右區間找第k-sum小)

時間複雜度為o(nlogn),空間複雜度o(n*4)

題目:每次查詢前x個數中的第k小,無修改

做法:這裡改變一下上面的方法。上面的做法中,可以發現,sum的大小表示的是所有數中\(≤\)mid的數的個數,而這裡是要求前x個數中\(≤\)mid的數的個數,於是我們需要對每乙個數a[i]加入之後都對前i個數建立一顆值域線段樹,詢問前x個數的時候就使用第x個線段樹

可持續化線段樹

顯然不可能真的建立n個值域線段樹

鏈結時空複雜度o(nlogn)

題目:查詢改為[ l , r ]區間,無修改

上面的字首第k小相當於把主席樹看做字首和,那麼區間第k小相當於使用差分相減

首先明確一件事,對於上面建的n個值域線段樹(假裝把n個樹都單獨拆出來),形態完全相同,並且對於每乙個樹的相同位置,意義幾乎一樣,比如,第x個樹和第y個樹的某個位置都表示不大於c的數的個數,只不過乙個是針對前a[1x],另乙個a[1y]。所以可以考慮字首和的思想,假設y \(>\) x,用y樹乙個節點減去x樹上對應節點就可以表示a[ x+1 ~ y ]這一段上不大於c的數

做法:對於查詢[ l , r ],同時使用l-1和r兩個值域線段樹,每次的sum由r樹的左兒子減去l-1樹的左兒子得到,向下遞迴時兩個根要一起向同乙個方向走

時空複雜度o(nlogn)

code:

#include#define n 200005

using namespace std;

int n,m;

int ref[n],len;

int a[n],ndsum;

int root[n],ls[n*20],rs[n*20],sum[n*20];

template void read(t &x)

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

void copynode(int x,int y)

int modify(int rt,int l,int r,int x,int val)

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

int main()

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

return 0;

}

區間靜態第k小運用了字首和差分,所以顯然這個問題可以上樹

將乙個點到根節點的路徑建值域線段樹,對於父親\(rt\)和兒子\(v\),\(v\)的值域線段樹從\(rt\)擴充套件乙個點而來;查詢路徑\((u,v)\)用差分轉換成\(root[u]+root[v]-root[lca(u,v)]-root(fa[lca(u,v)])\)四棵線段樹加減

#include#define n 100005

#define min(x,y) ((x)<(y)?(x):(y))

#define max(x,y) ((x)>(y)?(x):(y))

using namespace std;

typedef long long ll;

int n,m,a[n],b[n],len;

int f[n][18],dep[n];

int root[n],ndsum;

int sum[n*100],ls[n*100],rs[n*100];

struct edge

edge[n<<1];int head[n],cnt=1;

void add_edge(int from,int to)

template void read(t &x)

void build(int &rt,int las,int l,int r,int x)

void dfs(int rt,int fa)

}int lca(int x,int y)

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

int main()

order[n];

template void read(t &x)

void modify(int &rt,int l,int r,int pos,int v)//修改某一顆樹

int pre_modify(int pos,int v)//修改logn顆樹

int query(int l,int r,int k)//詢問

else }

int main()

if(order[i].o=='c')

exc[++len]=order[i].val;

} sort(exc+1,exc+len+1);

len=unique(exc+1,exc+len+1)-exc-1;

for(int i=1;i<=n;++i) pre_modify(i,1);//加入a[i],建樹

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

else

}return 0;

}

模板 區間第k小

我實在是太弱了現在才會這個東西qaq。主席樹做法。一張關於主席樹的無字說明 線段樹 2 是只單點修改了實心酒紅色點的線段樹 2 線段樹 2 中的藍色節點實際上就是線段樹 1 的藍色節點,我們只是把位址複製過來了。我們多開了乙個線段樹,但是節點數量卻只多了 log 層,那麼對於 n 的歷史版本保留就提...

主席樹(區間第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...