算法 {线段树SegmentTree}
@LOC_1
线段树
定义
性质
@DELI;
#綫段樹的高度#: log2(N)
;
#綫段樹的節點個數#: 2*N - 1
; (還沒證明 好神奇 不管N
是多少 節點個數都是2*N-1
個);
@DELI;
判斷一個問題能否用綫段樹 很簡單 就看你能否實現Update_leaf, Update_nonLeaf
這兩個函數;
.
這是應用綫段樹的核心! 只要這兩個函數能實現 就可以了;
. .
千萬不要去考慮Modify_single, Query_interval, Initialize
這些個函數 (因爲綫段樹很複雜 如果你一時忘記綫段樹的精髓 很容易誤入歧途…), 比如舉個例子 你可能去思考 對於修改操作A[x]=d
他要怎麽更新非葉子節點的數據域呢? 你一旦這麽想 就容易出錯… 因爲非葉子節點的數據域 不是通過A[x]=d
來維護的, 換句話説 他的數據域 其實是通過兩個兒子的數據域來更新的 跟A[x]=d
無關, 而且 其實很多時候 通過A[x]=d
這個信息 是無法更新非葉子節點的數據域, 但不代表 綫段樹的非法的, 詳見@LINK: @LOC_0
;
@DELI;
{Initialize, Modify_single, Query_interval}
這三個函數 他們維護數據域 都是通過Update_nonLeaf
函數, 所以要確保這個函數 他是O(1)
的;
令K: Update_nonLeaf函數的效率
, 則Initialize: 2*N(節點個數) * K
, Modify_single: logN(遞歸次數) * K
, Query_interval: logN(遞歸次數) * K
;
.
可以發現 瓶頸在於Initialize
函數上;
錯誤
#其實是正確的#: 綫段樹 只要不是樹套樹 一定是不需要維護UnderlyArray
底層數組的, 如果你認爲 你的Modify_single
函數 需要藉助到底層數組, 那麽我認爲 你的算法 一定是錯誤的…;
因爲 你只需要將A[x]=d
這個修改操作 去更新葉子節點的數據域 這很簡單 不需要藉助底層數組 因爲葉子節點就維護了一個A[x]
元素; 而對於非葉子節點 他的數據域 是通過Update_nonLeaf
來實現的(即通過兩個兒子來拼接) 完全不會使用到A[x]=d
這個信息;
@DELI;
@MARK: @LOC_0
;
對於修改操作(A[2]=d
), 比如對於非葉子節點[0,5]
, 你千萬不要去思考 執行這個修改操作后 會對該節點的數據域 產生怎樣的影響(即通過A[2]=d
操作 如何去更新[0,5]非葉子節點
的數據域), 這樣去思考綫段樹是錯誤的!
因爲[0,5]
的數據域 是由兒子的數據域 來更新的!!! 搞不清這一點 你很難懂綫段樹的精髓…
.
比如說數據域維護最大值, 那麽你這個A[2]=d
操作后 我們并不知道這個d
是否是此時[2-5]
裏面的最大值, 於是 你可能會認爲 是否是數據域裏維護的信息太少 把原來的int ma
改成是set<int> s
(樹套樹) 然後維護底層數組 從set
裏面把原來值刪掉 再添加新值, 這樣對於A[2]=5
操作 就可以直接來影響[2-5]
節點的數據域了; 這樣做 是會超時的… [2-5]
的數據域 只要能夠通過兩個兒子的數據域來更新即可, 他不是通過修改操作來更新數據域的!
.
具體 你要實際分析, 即你要分析Update_nonLeaf
這個函數的效率! 也不能說絕對就錯, 比如說 樹套樹算法裏 就是這麽乾的
也即是 綫段樹的維護 是通過回溯來進行的, 只要兩個兒子[l0,r0][r0+1,r1]
的信息是正確的 然後通過拼接來更新當前[l0,r1]
的信息 這點非常重要 而不是說通過A[d]=x
修改操作來更新[l0,r1]
的信息;
判斷修改操作(A[x]=d
) 是否可行 的正確評判依據是:
+(對於葉子節點 也就是[x-x]
這個區間): 確保A[x]=d
操作 可以直接更新其數據域;
+(對於非葉子節點): 確保他可以通過兩個兒子的數據域來更新; (不是通過A[x]=d
來更新的);
@DELI;
綫段樹 不一定是滿二叉樹;
所以使用__Nodes[ ind*2, ind*2+1]
這種方式 並不好! 比如N=5000
此時會使用到__Nodes[16000] (即N*4量級)
但實際上 綫段樹的節點個數 也就N*2量級
; 換句話説 你的__Nodes[0....16000]
裏面 其實有很多 你會用不到的!
.
比如舉個例子 對於[0-100]
區間的 一個小節點([0-2]
區間) 他會得到[0-2](x), [0-0](x*2*2), [1-1](x*2*2+1), [2-2](x*2+1)
, 可以發現 [2-2]
這個葉子節點 和[0-0], [1-1]
這些葉子節點 的深度是不同的! 也就是(x*2+1) * 2
是沒有使用的;
其實我們知道 綫段樹節點個數 就是N*2-1
個;
@DELI;
Update_bySon
裡 必須更新Interval
, 雖然對於樹中固有節點 這是重複操作, 但對於Query
你要保證其返回的節點裡的Interval
是正確的, 因為Query
返回的節點 也會使用Update_bySon
(而這個函數 裡面會用到Interval
);
.
這是錯誤的吧? Update_bySon
裡面 應該不會涉及到interval
把?
@DELI;
不要把segmentTree/splay
的 數據域 用一個自定義類(比如SumType
)來表示, 然後你可能認為 x
節點的數據域 就等於(x節點的信息) + 左兒子的數據域 + 右兒子的數據域
, 其實這是錯誤的 因為她不一定是符合累加的;
.
比如Seq_max/pre,suf
(區間最大前綴後綴), 她要同時根據兩個兒子的信息來更新, 不可以拆分開來; 即他們不是獨立的 需要一起來判斷;
@DELI;
不要以為 一個節點的區間是: (左兒子 + cur + 右兒子), 這是錯的 你當成Splay了… 沒有cur
, 即比如左兒子是[l1.r1]
右兒子是[l2,r2]
, 那麼 當前區間就是[l1, r2]
即一定有r1 == l2-1
, 比如[1,3] [4,6] -> [1,6]
, 而在Splay樹裡 他是[1,3] + 4 + [5,6] = [1,6]
;
算法
权值线段树
@LINK: https://editor.csdn.net/md/?not_checkout=1&articleId=138284344
;
模板
//{ ___SegmentTree_
struct ___SegmentTree_{
using UnderlyingArray_ = int; // 线段树维护的底层数组是`underlyingARRAY[0,1,...,__Range)`;
friend ostream& operator<<( ostream & _cout, ___SegmentTree_ const& _a){
_cout<< "___SegmentTree-Debug-Begin\n";
auto dfs = [&]( auto _dfs, Node_ * _cur)->void{
if( _cur->Info.Interval.first != _cur->Info.Interval.second){ _dfs( _dfs, _cur->sonPtr[0]); _dfs( _dfs, _cur->sonPtr[1]);}
_cout<< _cur->Info<< "\n";
};
if( _a.__RootNode != nullptr){ dfs( dfs, _a.__RootNode);}
_cout<< "___SegmentTree-Debug-End\n";
return _cout;
}
struct Node_{
struct Info_{ // 对于*非叶子节点* 其所有信息 必须可以由*其两个儿子*的信息 来推导出;
friend ostream& operator<<( ostream & _cout, Info_ const& _a){
return _cout<< TOstringITEMS_( _a.Interval, _a.Ma);
}
std::pair<int,int> Interval;
long long Ma;
void Merge( Info_ const& _lef, Info_ const& _rig){ // `lef=[0-1]; rig=[2-3]` 则当前区间是`[0-3]`, 你需要根据`lef,rig`的信息 来推导出`[0-3]`区间的信息;
Interval = {_lef.Interval.first, _rig.Interval.second};
Ma = std::max( _lef.Ma, _rig.Ma);
}
};
Info_ Info;
Node_ * sonPtr[2];
};
std::vector< Node_> __Nodes;
int __Range; // 线段树区间`[0...__Range)`;
Node_ * __RootNode;
void Initialize( UnderlyingArray_ const* _arr, int _length){
ASSERTsystem_( _length > 0);
__Range = _length;
__Nodes.resize( __Range*2); // The maximum of nodesCount is `Range*2`;
int usedNodes = 0;
auto dfs = [&]( auto _dfs, int _l, int _r)->Node_*{
auto cur = &__Nodes[ usedNodes ++];
if( _l == _r){ // `cur`是叶子节点
auto & info = cur->Info; info.Interval = {_l,_l};
//>> 使用`_arr[_l]`来更新`info`;
info.Ma = _arr[_l];
}
else{
int mid = (_l+_r)>>1; cur->sonPtr[0] = _dfs( _dfs, _l, mid); cur->sonPtr[1] = _dfs( _dfs, mid+1, _r);
cur->Info.Merge( cur->sonPtr[0]->Info, cur->sonPtr[1]->Info);
}
return cur;
};
__RootNode = dfs( dfs, 0, __Range-1);
}
void Modify_index( int _ind, UnderlyingArray_ const& _modVal){ // 对`underlyingARRAY[ _ind]`执行`modVal`的修改操作 (至于具体她是怎么的修改操作(赋值/累加)? 自己决定);
ASSERTsystem_( 0<=_ind && _ind<__Range);
auto Dfs = [&]( auto _dfs, Node_ * _cur)->void{
if( _cur->Info.Interval.first == _cur->Info.Interval.second){ // `cur`是叶子节点
auto & info = _cur->Info;
//>> 使用`_modVal`来更新`info`;
info.Ma = _modVal;
return;
}
if( _ind <= _cur->sonPtr[0]->Info.Interval.second){ _dfs( _dfs, _cur->sonPtr[0]);} else{ _dfs( _dfs, _cur->sonPtr[1]);} _cur->Info.Merge( _cur->sonPtr[0]->Info, _cur->sonPtr[1]->Info);
};
Dfs( Dfs, __RootNode);
}
Node_::Info_ Query_interval( int _l, int _r){
ASSERTsystem_( 0<=_l && _l<=_r && _r<__Range);
auto Dfs = [&]( auto _dfs, Node_ * _cur)->Node_::Info_{
if( _l<=_cur->Info.Interval.first && _r>=_cur->Info.Interval.second){ return _cur->Info;}
int mid = (_cur->Info.Interval.first + _cur->Info.Interval.second)/2; if( _r <= mid){ return _dfs( _dfs, _cur->sonPtr[0]);} else if( _l > mid){ return _dfs( _dfs, _cur->sonPtr[1]);}
Node_::Info_ ANS; ANS.Merge( _dfs( _dfs, _cur->sonPtr[0]), _dfs( _dfs, _cur->sonPtr[1]));
return ANS;
};
return Dfs( Dfs, __RootNode);
}
};
//} ___SegmentTree_
例题
@LINK: https://editor.csdn.net/md/?not_checkout=1&articleId=136540076
;
看一個需求: 對於一個集合A, 操作: {添加一個數add
, 查詢位於L < ? < R
區間裏的元素個數};
使用綫段樹+離散化可以解決這個問題, 把{A裏的元素, 所有會添加的數add
}(不包含L,R
)進行離散化, 然後把滿足L<?<R
的這個條件 轉換為 綫段樹裏的一個[l,r]
區間, 即讓綫段樹維護{單點修改A[x]+=1
, 區間查詢A[l...r]之和
}這個兩個操作;
.
其實這個也是個專門的算法: {動態查詢大於K的元素個數}
@DELI;
@LINK: https://editor.csdn.net/md/?not_checkout=1&articleId=134148990
;
維護A[]
數組, 進行A[l...r]+=K
修改,和A[l]^2+...+A[r]^2
查詢
@DELI;
@LINK: https://atcoder.jp/contests/abc322/tasks/abc322_f
;
數據域為: pre0, pre1, suf0, suf1, max0, max1
其中pre0
為前綴為0
的最長長度, suf
為後綴, max
為任意最長子數組;
懶標記為delay ^= 1
;
注意, Build
時 如果A[ind] = 1
那麼cur.pre1|suf1|max1 = 1
, 但是 千萬別忘記了A[ind]=0
的情況 此時你需要更新cur.pre0|suf0|max0 = 1
; 別只更新了A[ind]=1
的情況 而忘了=0
的情況;
@DELI;
AW-4878. 维护数组
.
维护三个值sum, presum, sufsum
, sum
是原生值, presum
需要min( sum, B)
, sufsum
需要min( sum, A)
;
@Delimiter
LeetCode-6358. 更新数组后处理求和查询
For a Boolean-Array ([0/1, 0/1, ...]
), there are two operations:
1 Reverse a interval
[
l
,
r
]
[l,r]
[l,r];
2 Get the number of 1
in the whole array;
@Delimiter
link
sustain many information (e.g., total sum, maximal prefix sum, maximal suffix sum, maximal sub-interval sum) on each nodes.
link
two operations: (get GCD of a interval) and (add a
v
a
l
val
val on a interval)
link
multi-Delays, one is Add, one is multiplicative-factor.