[数据结构入门]分治树(猫树)

#1.0 算法实现

#1.1 预处理

考虑一个序列的分治树,对于每个节点,设当前节点维护区间 \([l,r]\) 的中点为 \(mid\),预处理出所有形如 \([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\)),按层储存状态,保证空间复杂度。

由猫树处理的过程不难发现,所维护的信息必须满足区间可加性

在实际实现时,我们不必真正建立出左右儿子,只需分出区间进行预处理即可,因此,我们在存储信息时可采用二维数组,形如 VAL[MAX_D][N],其中 MAX_D 指的是最大层数。

如果我们按层储存,那么编号为 \(x,y\) 的点的 \(\texttt{LCA}\) 所在层数为 log2[x] - log2[x ^ y].

#2.0 例题

#2.1 区间最大子段和

可以发现,区间 \([l,r]\) 的最大子段和要么在 \([l,mid]\) 中,要么在 \([mid+1,r]\) 中,要么横跨 \(mid\),所以我们可以维护最大前后缀和来解决这个问题。

对于不横跨 \(mid\) 的,正常做最大子段和即可。对于横跨 \(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 = 1;
int s[25][N],p[25][N],lg[N];

/*s 是不能与 mid 断开的前后缀,p 是可与 mid 断开的最大子段*/

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;
    /*维护 mid 左边*/
    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;
        /*如果小于零了,后面的维护可以
        从中断开,保证最大值*/
    }
    /*维护 mid 右边*/
    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 = 1;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;
}

posted @ 2021-06-19 16:43  Dfkuaid  阅读(1270)  评论(0编辑  收藏  举报