算法 {线段树SegmentTree}

算法 {线段树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.

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值