1. 程式人生 > 其它 >[資料結構入門]分治樹(貓樹)

[資料結構入門]分治樹(貓樹)

#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;
}