關於實時開節點的可持久化線段樹及區間 樹鏈第K大值

2021-06-20 09:26:15 字數 3987 閱讀 5338

又扯回這道經典的可修改區間第k小值 dynamic ranking了:

一、樹套樹

之前寫過,挺慢的,也挺長的。

二、樹狀陣列套權值線段樹

如果沒有修改,可以直接開n棵權值線段樹,每一棵線段樹都是在前一棵的基礎上只修改一位。

每一棵線段樹也都是每乙個字首所代表的「權值線段樹」。

所以兩棵線段樹相減代表的就是乙個區間的"權值線段樹",就能在上面二分。

但是有修改了,每一次修改就會影響它後面的所有線段樹。

所以使用bit來維護這個字首和,每次只會修改o(logn)棵,就沒問題了。

每個區間也通過o(logn)棵線段樹的加加減減得到。

現在來計算時間複雜度:

每次修改影響o(logn)課線段樹,每棵線段樹修改需要影響o(logn)個節點,所以是o(log^2n)的。

而線段樹的節點也可以動態的開,每次修改需要的空間也是o(log^2n)級別的。

code1:

#include #include #include #include #include #include #include #include #include #include using namespace std;

#define n 10005

#define m 20005

int n , m , a[n];

int d[m] , d;

pairw[n];

int r[n];

int c[n];

#define node 2097152

#define mid int mid = l + r >> 1

#define left l , mid

#define right mid + 1 , r

int ch[node][2] , sum[node] , nodecnt;

int newnode()

void add(int& p , int l , int r , int x , int w)

}void add(int x , int w , int val)

int pp[20] , mm[20] , sp , sm;

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

else

}void work()

sort(d , d + d) , d = unique(d , d + d) - d;

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

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

else

}}int main()

但實際上由於線段樹的節點是完全動態開的,它的range完全可以設為數字的值域。

這樣只會增加線段樹的時空常數,從o(logn)變為o(log(range)),能簡化不少**。

但空間是很寶貴的……能離散化的時候盡量離散化吧。

#include #include #include #include #include #include #include #include #include #include using namespace std;

#define n 10005

int n , m , a[n];

int d = 1e9;

int c[n];

#define node 8388608

#define mid int mid = l + r >> 1

#define left l , mid

#define right mid + 1 , r

int ch[node][2] , sum[node] , nodecnt;

int newnode()

void add(int& p , int l , int r , int x , int w)

}void add(int x , int w , int val)

int pp[50] , mm[50] , sp , sm;

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

else

}void work()

else

}}int main()

query過程寫的比較暴力隨性……怎麼方便怎麼來……

然後今天推翻一切自己重寫才發現出乎意料地好想好寫……原來寫的那是什麼玩意啊 - -

再記一下關於樹上鏈第k大值的體會……

前幾天剛寫完暴力的樹鏈剖分線段樹套平衡樹 = = 

修改查詢的複雜度分別是o(log^2n)和o(log^4n),很嚇人。

也能使用可持久化線段樹賣空間來提高時間效率……

每棵線段樹記錄的是它到根所有經過的節點的權值。

這樣一條從x到y的鏈就可以用x+y-lca(x,y)-father(lca(x,y))得到(這裡是點權)。

考慮修改,修改乙個點權影響的是它的子樹,考慮dfs序列就是乙個區間。

所以使用另外一棵線段樹來維護dfs序列,實現區間增和單點查詢,這個是很好做的,也不用增加標記。

每一條從根出發的鏈的"權值線段樹"都能利用這棵線段樹,成為o(logn)棵"權值線段樹"的和,對"權值線段樹"查詢修改也需要o(logn)時間。

詢問和修改的時空複雜度就也是o(log^2n)級別的。

這道題空間非常緊……需要卡好空間限制才能過,如果不先離散化的話。

code:

#include #include #include #include #pragma comment(linker,"/stack:102400000,102400000")

using namespace std;

#define n 80005

int n , m , a[n] , pre[n] , mcnt;

struct edge

e[n << 1];

int dep[n] , f[17][n] , l[n] , r[n] , ncnt;

void dfs(int x , int fa)

int lca(int x , int y)

int down = -1 , up = 1e8 , t[n << 1];

#define node 12582912

int id(int l , int r)

#define mid int mid = l + r >> 1

#define left l , mid

#define right mid + 1 , r

int ch[node][2] , sum[node] , nodecnt;

int newnode()

void add(int& p , int l , int r , int x , int w)

}int pp[100] , mm[100] , sp , sm;

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

else

}void add(int l , int r , int top , int bot , int x , int w)

}void query(int l , int r , int x , bool flag)

}void work()

, pre[x] = mcnt ++;

e[mcnt] = (edge) , pre[y] = mcnt ++;

}dfs(1 , 0);

for (j = 1 ; 1 << j <= n ; ++ j)

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

f[j][i] = f[j - 1][f[j - 1][i]];

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

add(1 , n , l[i] , r[i] , a[i] , 1);

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

else

}}int main()

可持久化線段樹總結(可持久化線段樹,線段樹)

最近正在學習一種資料結構 可持久化線段樹。看了網上的許多部落格,弄了幾道模板題,思路有點亂了,所以還是來總結整理下吧。你需要維護這樣的乙個長度為 n 的陣列,支援如下幾種操作 在某個歷史版本上修改某乙個位置上的值 訪問某個歷史版本上的某一位置的值 此外,每進行一次操作 對於操作2,即為生成乙個完全一...

可持久化線段樹

可持久化線段樹,意思是可以查詢歷史記錄的線段樹。又叫主席樹。我們可以通過記錄不同的根節點,並在每乙個更新到的節點處新建必要的節點。詢問不同版本的主席樹,只需要進入不同的根節點即可。例題 給定n,m,輸入n個數組成的數列,有m個詢問,每次詢問l,r這個區間中,第k小的數的值。分析 這個題可以巧妙運用主...

可持久化線段樹

以p3919 模板 可持久化陣列 可持久化線段樹 平衡樹 為例。知識點 1.練習可持久化線段樹 2.線段樹維護數列。線段樹維護數列單點查詢僅需o logn 3.記得return root 4.記得設定左右兒子 5.有時需注意cnt的初始大小 include using namespace std i...