題目鏈結
前言:最近在做線段樹的練習,對於區間合併問題不是很清楚,花了好久才把線段樹的區間合併問題理清楚,所以把學習的過程記錄下來,建議手動建樹並模擬測試用例
題目大意:
有乙個陣列,求這個陣列中最長的單調連續遞增序列的長度
題解:見一下注釋
/*
樹結點的定義:
有該結點的左端點、右端點
有該結點對應區間的最左端點的值,最右端點的值
有該結點對應區間的從第乙個元素開始、從左到右的最長連續遞增子串行
有該結點對應區間的從最後乙個元素開始、從右到左的最長連續遞減子串行
線段樹的定義:
有該線段樹的樹結構
有定義樹結構的函式
有更新樹結構的函式
有查詢區間最長長度的函式
有向上更新父結點的函式
對於樹結構的實現:
使用node tree[n*4]來定義
對於定義樹結構的函式:
從0-n-1根據陣列a元素值來建樹
葉節點的左右端點、左右端點的值、左遞增右遞減的長度 顯然是已知的
對於非葉節點,需要根據左右子樹的情況來確定該結點的左遞增右遞減以及總連續遞增的長度,在pushup函式中封裝該功能
對於更新樹結構的函式:
在理解了樹結構定義的函式之後,樹的更新很好理解
只是在建樹的基礎上,只需要修改樹上的一條線就可以
對於向上更新父結點的函式:(比較難理解,劃重點)
這裡可以分為好幾種情況
首先,這個父結點的左右端點和左右端點的值很容易判定,直接使用左子樹的最左端點和最左端點的值以及右子樹的最右端點和最右端點的值
其次,這個父結點的左遞增長度和右遞減長度的判定:
這個長度肯定是等於左子樹的左遞增的長度
(這裡需要明確,左遞增的長度的含義:就是從該結點對應區間的最左端點開始,向右,連續的遞增的子串行的長度)
(再次需要明確,右遞減的長度的含義:就是從該結點對應區間的最右端點開始,向左,連續的遞減的子串行的長度)
(例如:對於陣列[3,4,2,5] 左遞增的長度為2(由3,4組成,2不算,因為2<4,使遞增結束)
右遞減的長度為2(由2,5組成,4不算,因為4>2,使得遞減結束)
另外,如果左子樹的最右端點的值是小於右子樹的最左端點的值,
而且如果左子樹的最長的連續遞增自學列的長度和左子樹的左遞增的長度相同(說明是左子樹的所有元素滿足時連續單調遞增的)
同樣,如果右子樹的最長的連續遞增自學列的長度和右子樹的右遞減的長度相同(說明是右子樹的所有元素滿足時連續單調遞增的)
對於查詢區間最長長度的函式:
不可以像其他線段樹那樣直接合併查詢,需要在合併查詢之前,判斷左子樹的最右端點值是否小於右子樹的最左端點值
*/#include
using
namespace
std;
const
int n = 100000;
int n,m;
int a[n];
struct node
int len()
};struct seg
if(tree[i*2+1].len() == tree[i*2+1].rm)
//不管左右子區間是不是完全遞增,反正從左子區間到右子區間是遞增的的
//那麼父結點的最長連續遞增的區間長度等於自身和左右長度加起來最大的乙個
tree[i].m = max(tree[i].m,tree[i*2].rm+tree[i*2+1].lm);
} //我不管從左到右是不是遞增的,我就要最大的
//至此,對於父結點i,
//它的從左到右的最長的遞增長度確定了 ,它的從右到左的最長的遞減長度確定了
//說明:如果第乙個if不滿足條件
//那麼從左到右的最長遞增和從右到左的最長遞減是不會變的
tree[i].m = max(tree[i].m,max(tree[i].rm,tree[i].lm));
}void build(int i,int l,int r) else
}void update(int i,int pos,int v)
} //查詢又是乙個大問題
int query(int i,int l,int r)else
return max(max(m1,m2),sum1+sum2);}}
} }seg;
int main()else seg.update(1,l,r);}}
return
0;}
下面再貼乙個不帶注釋的,方便瀏覽#include
using namespace std;
const int n = 100000;
int n,m;
int a[n];
struct node
int len()
};struct seg
if(tree[i*2+1].len() == tree[i*2+1].rm)
tree[i].m = max(tree[i].m,tree[i*2].rm+tree[i*2+1].lm);
} tree[i].m = max(tree[i].m,max(tree[i].rm,tree[i].lm));
}void build(int i,int l,int r) else
}void update(int i,int
pos,int v)
} int query(int i,int l,int r)else
return max(max(m1,m2),sum1+sum2);}}
} }seg;
int main()else seg.update(1,l,r);}}
return
0;}
注
個人拙見,如有更好演算法還望指出,謝謝!
參考部落格:
HDU 3308 LCIS(線段樹合併)
維護乙個區間的包含最左的元素的lcis,包含最右元素的lcis,以及整個區間的lcis,然後pushup的時候就更新這三個值就行了。注意要考慮左右兒子可以 接 起來的情況等等 查詢的時候要注意,也要考慮左右可以 接 起來時候,不過還要注意,有可能左右兒子邊界的已經 越界了 就是超過了查詢範圍,還要取...
HDU 3308 LCIS(線段樹區間合併)
給你乙個序列,現在進行一些操作,一種是詢問某一段最長連續上公升子串行 lcis 的長度,另乙個就是修改某個點的值 區間合併的簡單題 這裡的區間合併,要判斷的是左兒子最右邊的值和右兒子最左邊的值的關係,那麼我們這道題要維護的東西就有 最左端開始的lcis,包括最右端的lcis,該區間的lcis,最左邊...
hdu3308 LCIS 線段樹 區間合併
n n 1e5 個數,m m 1e5 種操作,操作分兩種,u a b 0 aq a b 0 a b區間合併經典題目,每個點維護三個值,從左最長,從右最長和整段最長 注意合併的時候,要判斷a mid 1 a mid 時,才能從中間拼接,否則不能拼接 其餘部分,和區間合併沒區別,注意詢問別越界 incl...