又扯回這道經典的可修改區間第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...