線段樹上二分搜與經典題
如果你認為他只能做到單點修改,區間詢問,那麼就太少了!
在前一個章節,我們學會了單點修改、區間詢問的線段樹
單點修改 區間詢問線段樹上二分搜
而現在呢?如果我們有一道問題如下
在一個陣列中尋找第一個出現的大於 的位置,同時也會有修改元素的操作
這樣的問題,線段樹也辦得到!如果要尋找第一個數字,該怎麼做呢?
我們可以試著用一棵區間詢問最大值的線段樹來幫我們做到這一點

當我們建立完一棵區間最大值線段樹之後,我們可以在樹上去尋找我們要的位置,如下圖

這樣的動作一般稱為「線段樹上二分搜」,如同圖上,我們先從最上面那個點開始找
大致操作如下
int find(int x, int idx, int l, int r){
if(l==r) return l; //找到了就回傳
int m = (l+r)/2;
//往兩邊找,如果左邊的最大值>=x,往左繼續找
//否則往右找
if(tr[idx*2] >= x) return find(x, idx*2, l, m);
else return find(x, idx*2+1, m+1, r);
}
學會怎麼做之後,當然就是要來做練習題囉
線段樹維護動態規劃(Dynamic Programming)
有些動態規劃的題目,我們可以將轉移式放到線段樹上,來幫助我們尋找答案
而這裡,我們以「區間最大連續和」為例
問題如下:
給一個 項的陣列,以及 個操作或詢問
每次操作修改一個位置的值
每次詢問區間 之間的區間最大連續和
這個問題,你可能會很直覺的想到動態規劃中所學到的 解法
但是,今天我們有次詢問,每次詢問都跑 ,那總時間會高達 !
不過,這樣的問題我們可以使用線段樹來輔助達成多次詢問
我們在前一章節所學的線段樹,大多都是維護總和/最大值等等
而如果我們將其改成維護區間最大連續和呢?
int query(int ql, int qr, int idx, int l, int r){
if(ql <= l && r <= qr){
return tr[idx];
}
int m = (l+r)/2;
if(ql > m){
return query(ql, qr, idx*2+1, m+1, r);
}
if(qr <= m){
return query(ql, qr, idx*2, l, m);
}
//修改合併函數!
return combine(query(ql, qr, idx*2, l, m), query(ql, qr, idx*2+1, m+1, r));
}
首先,我們該如何維護一個節點的答案呢?
我們可以寫成以下四個式子,作為每個節點所需維護的值的轉移式
(用 表示答案、 表示總和、 表示前/後綴的最大總和, 為區間)
這四個式子為這一題維護答案的四個轉移式
第一個式子表示的是一個區間的答案,會是左右區間的答案,或左區間的後綴最大總和與右區間的前綴最大總和相加
第二與第三個式子則是維護前綴最大總和與後綴最大總和
第四個式子是將左右區間的總和相加,則為此區間的總和
這四種操作能完美的解決這個問題!
以下為實作程式碼
struct node{
ll ans, left, right, sum;
node(){}
node(ll a, ll b, ll c, ll d){
ans = a, left = b, right = c, sum = d;
}
};
node combine(node a, node b){
node c;
c.ans = max({a.ans, b.ans, a.right+b.left});
c.left = max(a.sum+b.left, a.left);
c.right = max(a.right+b.sum, b.right);
c.sum = a.sum + b.sum;
return c;
}
A: 即範例題,在每次修改後輸出整個陣列的答案
Last updated
Was this helpful?