本文參考自算法發明者 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; }