[資料結構入門]分治樹(貓樹)
#1.0 演算法實現
#1.1 預處理
考慮一個序列的分治樹,對於每個節點,設當前節點維護區間 \([l,r]\) 的中點為 \(m\),預處理出所有形如 \([i,mid](l\leq i \leq mid)\) 的值與 \([mid+1,j](mid+1\leq j\leq r)\) 的值,這個過程的時間複雜度為 \(O(n\log n).\)
#1.2 查詢
對於任何區間查詢 \([l,r]\),分治樹上都存在一個對應區間包含 \([l,r]\),且中點 \(m\) 在 \([l,r]\) 內的節點,找到此節點,將預處理的 \([l,m]\) 和 \([m+1,r]\) 的資訊合併,即可得到詢問的答案。
#1.3 優化查詢複雜度
直接查詢這個節點的複雜度是 \(O(\log n)\),可以將序列長度擴充至 \(2\) 的冪(維護區間為 \([1,2^k]\)),我們考慮將這個區間建成一顆分治樹,顯然這是一顆滿二叉樹,那麼,我們要找的節點必然是 \([l,l]\) 和 \([r,r]\) 的 \(\texttt{LCA}\),我們採用堆式儲存,即節點 \(i\) 的左兒子編號為 \(2i\),右兒子為 \(2i+1\),觀察其二進位制形式,不難發現,每一個左兒子的編號相當於父結點編號左移一位,右兒子則是左移一位加一,所以代表 \([l,l]\) 和 \([r,r]\) 這兩個區間的兩個節點的 \(\texttt{LCA}\)
至於這兩點的編號是多少,我們可以在預處理時提前儲存,這樣,我們單次查詢的時間複雜度為 \(O(1)\)。
假設節點編號分別為 \(x,y\),那麼,他們的 \(\texttt{LCA}\) 編號便是 x >> log2[x ^ y]
,這裡 log2[k]
儲存的是 \(k\) 在二進位制表示下有多少位。
#1.4 其他
至於空間複雜度,考慮每一層儲存的狀態,每層有 \(n\) 種,一共有 \(\log n\) 層,故總體為 \(O(n\log n)\)。在實現時對於每個節點應當採用動態儲存,如 vector
或指標,或者提前計算層數(\(\log n\)
由貓樹處理的過程不難發現,所維護的資訊必須滿足區間可加性。
#2.0 例題
#2.1 區間最大子段和
可以發現,區間 \([l,r]\) 的最大子段和要麼在 \([l,mid]\) 中,要麼在 \([mid+1,r]\) 中,要麼橫跨 \(mid\),所以我們可以維護最大前後綴和來解決這個問題。
/*SPOJ GSS1 https://www.spoj.com/problems/GSS1/ */
const int N = 1000100;
const int INF = 0x3fffffff;
int loc[N],a[N],n,m,len = 2;
int s[25][N],p[25][N],lg[N];
inline int Max(const int &a,const int &b){
return a > b ? a : b;
}
inline void build(int k,int l,int r,int d){
if (l == r) {loc[l] = k;return;}
int mid = (l + r) >> 1;
int pre,sm;
s[d][mid] = a[mid],p[d][mid] = a[mid];
pre = sm = a[mid];sm = sm > 0 ? sm : 0;
for (int i = mid - 1;i >= l;i --){
pre += a[i],sm += a[i];
s[d][i] = Max(s[d][i + 1],pre),
p[d][i] = Max(p[d][i + 1],sm);
sm = sm > 0 ? sm : 0;
}
s[d][mid + 1] = a[mid + 1],
p[d][mid + 1] = a[mid + 1];
pre = sm = a[mid + 1]; sm = sm > 0 ? sm : 0;
for (int i = mid + 2;i <= r;i ++){
pre += a[i],sm += a[i];
s[d][i] = Max(s[d][i - 1],pre);
p[d][i] = Max(p[d][i - 1],sm);
sm = sm > 0 ? sm : 0;
}
build(k << 1,l,mid,d + 1);
build(k << 1 | 1,mid + 1,r,d + 1);
}
inline int query(int l,int r){
if (l == r) return a[l];
int d = lg[loc[l]] - lg[loc[l] ^ loc[r]];
return Max(Max(p[d][l],p[d][r]),s[d][l] + s[d][r]);
}
int main(){
scanf("%d",&n);
while (len < n) len <<= 1;
for (int i = 1;i <= n;i ++)
scanf("%d",&a[i]);
for (int i = 2;i <= len << 1;i ++)
lg[i] = lg[i >> 1] + 1;
build(1,1,len,1);
scanf("%d",&m);
while (m --){
int l,r;
scanf("%d%d",&l,&r);
printf("%d\n",query(l,r));
}
return 0;
}