算法 {动态区间覆盖}
动态区间覆盖
定義
[0,N)
的整數域裡 每個點有一個權值V[x]
, 有若干個操作[l, r](都是[0,N)整數), add/del
, 如果是add
表示讓一維空間上 放置一個[l,r]
的區間, 否則del
表示把[l,r]
這個區間刪除掉(如果有多個 則刪除一個)(此時一定保證當前是有[l,r]
這個區間); 每次操作後 詢問有所有被覆蓋的點的權值之和;
.
比如 第一次操作{[0,3] 1
, 此時答案為V[0]+V[1]+V[2]+V[3]
}, 第二次操作{[2,4] 1
, 此時答案為V[0,1,2,3,4]之和
}, 第三次操作{[0,3] 0
, 此時答案為V[2,3,4]之和
};
性質
看一個簡單的版本(不支持動態) 即有若干個區間 求他的並集的權值和;
此時可以進行區間合併, 比如[0,2] [1,3] [5,6] [6,7]
進行區間合併後為[0, 3] [5,7]
, 那麼答案為V[0...3] + V[5...7]
;
@DELI;
@DELI;
比如[l,r]
並不是[0,N)
範圍的整數, 而是很大 或者是浮點數, 此時你可以進行離散化哈希 把他映射到[0,N)
裡;
比如說 有[a, b, c, d, e]
這些點(浮點數/負數 比如a=-188.4, b=-23, c=0.4, d=1, e=3
), 操作[l, r] add/del
表示將該區間覆蓋/取消覆蓋, 最終查詢 被覆蓋的區間長度, 注意 查詢的 不再是點的權值, 而是區間長度; 比如說 [a,c] add; [b,d] add
, 那麼此時被覆蓋的區間是[a...d]
這個區間, 於是答案是d-a
(兩點間的距離);
因此 你需要把每個區間a-b, b-c, c-d, d-e
映射到線段樹的點上, 即線段樹的點是0: a-b, 1:b-c, 2:c-d, 3:d-e
(即5個點, 但線段樹裡是有4個點), 比如線段樹裡的[1,3]
這個區間 對應b-c, c-d, d-e
, 即b-e
這個區間長度, 我們令Hash[x]
表示他的離散化(即Hash[01234] = abcde
), 那麼線段樹裡的[1,3] = hash(3+1) - hash(1)
;
@DELI;
對於權值 你要保證 對於任何區間[l,r]
可以O(1)
的 獲取該區間的所有點的權值之和! 比如通過預處理等方法 總之要O(1)
的獲取該區間裡所有點的權值和;
算法
動態維護區間覆蓋問題的特殊線段樹
性質
線段樹的本質特性是 他的數據域 要滿足Fa.data = Lson.data + Rson.data
也就是當前節點的信息 一定可以由 兩個兒子的信息來推導出, 但是 我們這裡介紹的這個線段樹 他只是說 結構上 與傳統線段樹是一樣的, 但是 他本質上 跟正規線段樹是完全不一樣的!
再比如 傳統線段樹裡 非葉子節點 其實 他並不重要, 因為 傳統線段樹 他維護的 就是一個[0, N)
的數組 他和線段樹的葉子節點 是一一對應的, 非葉子節點 只是為了效率而設定的; 而這裡這個特殊線段樹 他非常特殊 他的非葉子節點 是獨立的 即他和葉子節點 是同等地位;
#錯誤#
先看一個錯誤的線段樹定義, 令當前的區間並集為
T
T
T, 對於線段樹的一個節點 [l,r]
, 他的信息為: Sum, Delay
, Sum
表示T與[l,r]
的交集裡 所有的點的權值之和 (比如說[l=4,r=6]
, T=[0,1] + [3,5]
, 那麼該節點Sum
等於V[4] + V[5]
), 然後Delay
表示 當前區間被覆蓋的次數;
如果沒有刪除操作 這個線段樹是合格的, 即 Modify_node
這個函數 是可行的, 但是一旦有刪除操作Modify_node( cur, val)
此時val==-1
(將該節點的覆蓋 -=1
) 此時cur.Sum
是無法更新的(你不能藉助他的兒子信息 這是該函數的硬性要求), 你可能認為 那再添加一個Cont
數據域 表示該區間的覆蓋次數, 可是 當Cont==1, val==-1
時 此時會導致Cont==0
然後cur.Sum
依然是無法更新的 (他不一定是
0
0
0, 因為他的兒子裡面可能會有覆蓋 比如[0,0] add; [5,5] add; [0,5] add; [0.5] del;
(每個區間都是線段樹的節點) 當執行最後一個操作時 此時會得到導致[0,5].Cont==0
此時他的Sum
並不是
0
0
0 因為[0,0], [5,5]
是有覆蓋的);
因此 樸素線段樹 可以解決(沒有刪除操作)的動態區間覆蓋問題, 但無法解決含有刪除操作的動態區間覆蓋問題;
這裡有個性質, 即我們只會查詢整個區間, 而樸素線段樹他要維護任意子區間; 因此 正確的做法是: 他只會保證根節點的信息是正確的, 而其他非根節點的信息 其實是錯誤的; 思路確實比較複雜 這裡直接將他的做法;
注意 此時這個線段樹 他只是在結構上和標準線段樹一致(即根節點是[0,N)
區間 然後分割為2個兒子…) 但是他的數據域 所維護的信息 並不滿足線段樹/懶標記線段樹的性質, 因此 不要用通常線段樹的思路去思考他;
每個線段樹節點c維護Sum, Cont
, Sum
表示當前節點c和他的所有子節點的覆蓋長度, Cont
為該節點的出現次數;
每個區間操作 [l, r] add/del
, 他按照線段樹的分割 會得到若干個節點, 比如[2, 6]
區間 對應到線段樹 分割成a:[2,3], b:[4,6]
兩個區間, 然後執行a.Cont += 1/-1, b.Cont += 1/-1
, 此時a,b,以及他倆的所有父節點
這些節點的Sum
屬性的計算式為if( x.Cont >= 1){ x.Sum = x區間被全覆蓋了} else{ x.Sum = 兩個兒子的Sum之和}
;
我們舉個例子, [0, N) add
, 此時會導致: 根節點.Cont = 1, 根節點.Sum = [0,N)整個區間的權值
, 注意 此時其他節點的信息 Cont=Sum=0
, 換句話說 比如 如果說你去查詢[0,1]
這個區間 會得到他的Sum==0
, 可是這個區間他其實是被[0,N)
覆蓋了的, 也就是說 你只能查詢根節點;
我們再看Sum
的定義, 他是說 當前節點和他的所有子節點的覆蓋長度, 什麼意思? 當你[0,N) add/del
時 他只對應一個節點r(根節點)
, 當你進行[l1,r1] add, [l2,r2] add, [l?,l?] del
若干個操作 令這些區間所對應的線段樹節點為{ni}
(比如[l1,r1] add/del
對應節點為n1, n2, n3
那麼就執行n1|n2|n3.Cont += 1/-1
), 那麼對於一個線段樹節點x = [l,r]
, 令{nni}
為{ni}
裡 x
的兒子節點集合, 那麼x.Sum
的值 就是{nni}
的覆蓋情況 (比如x=[1,5]
, {nni} = [1,3](Cont=1), [1,1](Cont=1), [4,4](Cont=1)
那麼此時x.Sum = [1,3]+[4,4]
); 但注意x.Cont
的值 跟{nni}
的覆蓋情況沒有關係, Cont
很簡單 就是看最開始[l,r] add/del
時 是否會分割為當前x
節點 只有這1種情況會決定Cont
的取值;
比如一個操作[l,r] add/del
他對應線段樹的a,b
節點, 那麼他操作完後 一定會保證: a-...-樹根, b-...-樹根
這兩條路徑上的所有點的Sum
值 一定是正確的, 因此 樹根的Sum
一定是正確的;
代碼
template< class _TypeSum_> class ___DynamicIntervalCoverage{
//< 有一個數組`A[0....N)`, 兩個操作`[l,r] add/del` 表示覆蓋/取消一次覆蓋`A[l...r]`這個區間, 線段樹維護的 就是這個區間`[0,N)`;
// . 每次操作后 求所有被覆盖的`A[i]`之和;
public:
void Initialize( int _range, std::function<_TypeSum_(int,int)> const& _funcSum){
__Get_ArraySum = _funcSum;
__Nodes.resize( _range * 4); __Range = _range;
__Build( 1, 0, __Range - 1);
}
void Modify( int _left, int _right, int64_t _val){ // `val>0` 將`A[l...r]`這個區間*覆蓋*`val`次, `val<0`將該區間*取消覆蓋*`val`次(確保此時區間已經至少覆蓋了`val`次);
ASSERT_SYSTEM_( 0<=_left && _left<=_right && _right<__Range);
__Modify_val = _val; __Modify_left = _left; __Modify_right = _right;
__Modify( 1, 0, __Range - 1);
}
_TypeSum_ Query(){ // 所有被覆盖的`A[i]`之和
return __Nodes[ 1].Sum;
}
// @DELI;
std::function<_TypeSum_(int,int)> __Get_ArraySum; // `__Get_ArraySum(l,r) = A[l...r]之和`;
struct __Node_{
public:
_TypeSum_ Sum;
int Count; // 始終`>=0`
friend ostream& operator<<( ostream & _cout, const __Node_ & _a){
return _cout<< "{Sum:"<< _a.Sum<< ", Count:"<< _a.Count<<"}";
}
};
vector<__Node_> __Nodes;
int __Range;
int __Modify_left, __Modify_right, __Query_left, __Query_right;
int64_t __Modify_val;
void __Update_fa( __Node_ & _fa, const __Node_ & _lson, const __Node_ & _rson, int _lef, int _rig){
if( _fa.Count > 0){ _fa.Sum = __Get_ArraySum( _lef, _rig);}
else{ _fa.Sum = _lson.Sum + _rson.Sum;}
}
void __Build( int _node_id, int _left, int _right){
auto & cur = __Nodes[ _node_id];
if( _left == _right){
cur.Sum = 0; cur.Count = 0;
return;
}
auto mid = (_left + _right)>>1;
__Build( _node_id<<1, _left, mid); __Build( _node_id<<1|1, mid+1, _right);
__Update_fa( __Nodes[ _node_id], __Nodes[ _node_id<<1], __Nodes[ _node_id<<1|1], _left, _right);
}
void __Modify( int _node_id, int _left, int _right){
if( ( _left >= __Modify_left) && ( _right <= __Modify_right)){
auto & cur = __Nodes[_node_id];
bool is_leaf = (_left == _right);
if( __Modify_val >= 1){
ASSERT_SYSTEM_( cur.Count >= 0);
cur.Sum = __Get_ArraySum( _left, _right);
cur.Count += __Modify_val;
}
else{
cur.Count += __Modify_val;
ASSERT_SYSTEM_( cur.Count >= 0);
if( cur.Count >= 1){ cur.Sum = __Get_ArraySum( _left, _right);}
else{
if( is_leaf){ cur.Sum = 0;}
else{ cur.Sum = __Nodes[_node_id<<1].Sum + __Nodes[_node_id<<1|1].Sum;}
}
}
return;
}
auto mid = (_left+_right)>>1;
if( (__Modify_left <= mid) && (__Modify_right > mid)){
__Modify( _node_id<<1, _left, mid);
__Modify( _node_id<<1|1, mid + 1, _right);
}
else if( __Modify_right <= mid){
__Modify( _node_id<<1, _left, mid);
}
else if( __Modify_left > mid){
__Modify( _node_id<<1|1, mid + 1, _right);
}
__Update_fa( __Nodes[ _node_id], __Nodes[ _node_id<<1], __Nodes[ _node_id<<1|1], _left, _right);
}
friend ostream& operator<<( ostream & _cout, const ___DynamicIntervalCoverage & _o){
_cout<< "___DynamicIntervalCoverage-Debug-Begin\n";
function<void(int,int,int)> dfs = [&]( int _id, int _l, int _r){
_cout<< "["<< _l<< ","<< _r<< "]: "<< _o.__Nodes[_id];
if( _l == _r){ return;}
auto mid = ( _l + _r) >> 1;
dfs( _id<<1, _l, mid); dfs( _id<<1|1, mid+1, _r);
};
dfs( 1, 0, _o.__Range-1);
_cout<< "___DynamicIntervalCoverage-Debug-End\n";
return _cout;
}
}; // class ___DynamicIntervalCoverage
例題
求水平矩陣的並集的面積 @LINK: https://editor.csdn.net/md/?articleId=134398933
;