貓樹學習筆記

本文參考自算法發明者 immortalCO(貓錕) 的博客 一種高效處理無修改區間或樹上詢問的數據結構(附代碼)git

感謝 貓錕 提供了對於一類題比較通用的解決辦法,以及思路啓發。算法

問題描述

給出一個某種元素的序列 \(a_1,a_2,\dots ,a_n\),要求進行 \(m\) 次詢問,每一次是詢問一段區間 \([l,r]\) 的某種支持結合律和快速合併的信息,要求在線。數據結構

這類問題比較通用,好比在 DP 的優化中就經常見到。優化

算法實現

算法介紹

對於常規問題,好比區間最值,區間最大子段和。咱們經常能用線段樹等數據結構達到,構造 \(O(n)\) ,詢問 \(O(\log n)\) 的時間複雜度。ui

對於這些作法,只有一點很差,詢問複雜度 不夠優秀,且對於一些特定問題,線段樹的 push_up 合併也很差寫。spa

但對於區間最值這類的問題, 咱們能夠相似 \(RMQ\) 那樣,在通常的問題上,以預處理的時間和空間,換取快速的詢問debug

咱們首先考慮詢問一個區間 \([q_l, q_r]\) 。若是 \(q_l = q_r\) ,就能夠直接獲得答案。不然會不斷在線段樹上定位,並且會在幾個區間的中點 \(mid\) 處被分開。code

我習慣於線段樹每一個區間維護的一個閉區間 \([l, r]\) ,其中中點 \(\displaystyle mid = \lfloor \frac{l + r}{2} \rfloor\)blog

考慮第一次被分開的位置,假設爲 \(p\) 。那麼原來的區間 \([q_l, q_r]\) 就被分爲 \([q_l, mid]\) ,與 \([mid + 1, q_r]\)

咱們考慮對於每個 \(mid\) 預處理他向前的後綴 \([i, mid]\) 的答案(其中 \(l \le i \le mid\)) ,以及他向後的前綴 \([mid + 1, j]\) (其中 \(mid + 1 \le j \le r\))。

若是咱們知道了 \(p\) 點所在的位置,咱們能夠直接利用以前預處理的 \([q_l, mid]\) 以及 \([mid + 1, q_r]\) 的答案直接合並便可。

不難發現預處理的複雜度是 \(O(n \log n)\) 的(對於每一層每一個數恰好被考慮一次 \(O(n) \times O(\log n) = O(n \log n)\)


而後怎麼快速知道這個位置呢?

不難發現這個 \(p\) 的位置,就是線段樹上對應 \([l, l]\)\([r, r]\) 節點的 \(lca\) (最近公共祖先)所處的位置,咱們能夠考慮用 \(st\) 表預處理,而後能夠直接查詢 \(p\) 的位置,但這樣顯然太麻煩了。

若是整棵線段樹知足堆式存儲(也就是對於點 \(i\) ,它的兩個兒子分別爲 \(2 i\)\(2i + 1\) ),就有一個很好的性質。

對於任意兩個同深度的點,他們的 \(lca\) 是他們二進制下 \(lcp\) (最長公共前綴)。

這個是十分顯然的,由於對於兩個深度相同的點,他們第一次分開的位置,必然致使當前最後一位的二進制不一樣,而前面都是相同的。

咱們把整棵樹建成一個滿二叉樹 \([1, 2^k]\),那麼對於任意一個區間 \([i, i]\) 都是知足他們的深度是最深且在同一層的。

注意對於不一樣深度的點不必定知足這個狀況!!這就是爲何咱們爲何要建滿的緣由。

而後對於兩個數 \(x, y\) 的二進制下的 \(lcp\)x >> Log2[x ^ y] 。(這個很顯然,丟掉第一個不相同的位後面的全部位就好了)

這樣咱們就能夠實現詢問 \(O(1)\) 啦。

咱們稱這個數據結構爲 貓樹

算法本質

看了一下 UOJ 評論區。。。

其實就是將分治進行離線,咱們用一個東西來存儲這個分治結構,以及前面按位置分治的答案。

因此這個算法最重要的仍是,尋找特定問題的分治方案。

例題講解

區間最大子段和

題意

給你一個序列 \(a_i\) ,有 \(m\) 次詢問,每次詢問一個區間 \([l, r]\) ,表示詢問這段區間的最大子段和。

題解

若是沒有區間,那麼這個是個經典的分治問題。

最大子段和,要麼徹底在左區間,要麼徹底在右區間,要麼跨越中點。

因此咱們只須要預處理 \([i, mid]\) 的最大前綴和與 \([mid + 1, j]\) 的最大前綴和,這個一邊遍歷一邊取 \(\max\)

以及這兩個區間的最大子段和。至於那個最大子段和,能夠利用前綴和相減,保存一個前綴和最小值就好了。

這個算法比標準線段樹上合併信息,要好寫而且更快。

代碼

對於第一道題,仍是建議看看代碼怎麼寫的。。(瓶頸在輸入輸出上也是沒誰啦)

#include <bits/stdc++.h>

#define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << x << endl
#define DEBUG(...) fprintf(stderr, __VA_ARGS__)

using namespace std;

inline bool chkmin(int &a, int b) {return b < a ? a = b, 1 : 0;}
inline bool chkmax(int &a, int b) {return b > a ? a = b, 1 : 0;}

inline int read() {
    int x = 0, fh = 1; char ch = getchar();
    for (; !isdigit(ch); ch = getchar()) if (ch == '-') fh = -1;
    for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48);
    return x * fh;
}

inline void Out(int x) {
    static char sta[18], top, flag = false;
    if (!x) { puts("0"); return ; }
    sta[top = 1] = '\n';
    if (x < 0) flag = true, x = -x;
    for (; x; x /= 10) sta[++ top] = (x % 10) + 48;
    if (flag) putchar ('-'), flag = false;
    while (top) putchar (sta[top --]);
}

void File() {
#ifdef zjp_shadow
    freopen ("1043.in", "r", stdin);
    freopen ("1043.out", "w", stdout);
#endif
}

const int N = 50100, Maxn = N * 4, MaxLog = 20, inf = 0x7f7f7f7f;

int pos[Maxn], val[Maxn], Log2[Maxn], maxlen;

namespace CatTree {

    inline int Max(int a, int b) { return a > b ? a : b; }

    int Pre[MaxLog][Maxn], Sum[MaxLog][Maxn];

    void Build(int o, int l, int r, int dep) {
        if (l == r) { pos[l] = o; return ; }
        int mid = (l + r) >> 1, sum, minv;

        Sum[dep][mid] = Pre[dep][mid] = sum = minv = val[mid]; chkmin(minv, 0);
        Fordown(i, mid - 1, l) {
            Pre[dep][i] = Max(Pre[dep][i + 1], sum += val[i]);
            Sum[dep][i] = Max(Sum[dep][i + 1], sum - minv);
            chkmin(minv, sum);
        }

        Sum[dep][mid + 1] = Pre[dep][mid + 1] = sum = minv = val[mid + 1]; chkmin(minv, 0);
        For (i, mid + 2, r) {
            Pre[dep][i] = Max(Pre[dep][i - 1], sum += val[i]);
            Sum[dep][i] = Max(Sum[dep][i - 1], sum - minv);
            chkmin(minv, sum);
        }

        Build(o << 1, l, mid, dep + 1);
        Build(o << 1 | 1, mid + 1, r, dep + 1);
    }

    inline int Query(int l, int r) {
        if (l == r) return val[l];
        register int dep = Log2[pos[l]] - Log2[pos[l] ^ pos[r]];
        return Max(Max(Sum[dep][l], Sum[dep][r]), Pre[dep][l] + Pre[dep][r]);
    }

}

int n;

int main() {

    File();

    n = read();
    For (i, 1, n) val[i] = read();

    for (maxlen = 1; maxlen < n; maxlen <<= 1);
    CatTree :: Build(1, 1, maxlen, 1);


    For (i, 2, maxlen << 1) Log2[i] = Log2[i >> 1] + 1;

    for (register int m = read(), l, r; m; -- m) {
        l = read(), r = read();
        Out(CatTree :: Query(l, r));
    }

#ifdef zjp_shadow
    cerr << (double) clock() / CLOCKS_PER_SEC << endl;
#endif 

    return 0;

}
相關文章
相關標籤/搜索