算法 {动态区间覆盖}

算法 {动态区间覆盖}

动态区间覆盖

定義

[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;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值