猫树学习笔记

最近觉得线段树比较可爱~

所以就鸽着吧

 

知识来源:https://www.luogu.org/blog/yestoday/mao-shu

由于    对于我这样的一个蒟蒻线段树暂时还不可学,选择学习猫树

注:据知识来源表明,猫树难以用来区间修改

关于此数据结构的时空复杂度分析请参见知识来源

猫树支持维护满足结合律且支持快速合并的信息(摘自知识来源)

我的理解:

在小学课本中表述如下:

加法结合律:三个数相加,先把前面两个数相加,再加第三个数,或者先把后面两个数相加,再和第一个数相加,它们的和不变————360百科:结合律

关于另一个条件“快速合并”,再摘一段知识来源中的思想:以一维数据的线段查询为例,若可以将该区间分成两部分,且这两个部分预先都被处理过,且这两个区间可以合并成当前查询区间,就可以快速得到答案。


 

举个栗子

 

我们要维护区间和

可以将当前序列分成左右两部分,从中间点向左右遍历,得到某一点到中间点的区间和

这样,如果要查询的区间的左右端点分别在此区间的左右两端,那么就可以快速合并出这一段区间的区间和。

 

但是如果要查询的区间同时落在当前序列中间点的左边或右边呢?

那么可以将这一边的序列再次进行 二分 的序列维护。

那么问题来了,每次区间的二分维护,遍历出来的结果一定是与上一层的二分遍历出来的结果不同的(因为中间点不同),

就要根据递归深度确定每一层的存储位置,

可以想到,如果我们维护的序列长度不超过 1e6,那么只需要20层的预处理空间。

为什么呢?因为每一次对当前递归层的每一段进行二分,下一层得到的“段数”总是这一层的2倍,

当无法二分时,下一层也就不存在了。

而1e6长度的数列,最多有1e6个数(废话),2的20次幂为1048576,也就是说,对于长度为1048576的数列,二十层也够用,1e6的长度也不用说了。

 

代码细节请看知识来源的代码,在此就不抄了。


来自知识来源的猫树例题代码:

----------2019,5,17更新。

https://www.luogu.org/recordnew/show/19119205

不粘贴了,强行模仿码风,define好用啊qwq hhhhha

 

#include<iostream>
#include<cstdio>
using namespace std;
const int MAXN = 50001;
#define ls (ind<<1)
#define rs (ind<<1|1)
#define mid ((l+r)>>1)

int a[MAXN<<1], s[21][MAXN<<2], p[21][MAXN<<2], pos[MAXN], lg[MAXN<<2];

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

int read() {
    char c = getchar();
    int x = 0, f = 1;
    while(c < '0' || c > '9') {
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9') {
        x = x*10 + c - 48;
        c = getchar();
    }
    return x*f;
}

void build(int ind, int l, int r, int d) {
    if(l == r) {
        pos[l] = ind;
        return;
    }

    int prep = a[mid], sm = a[mid];
    sm = Max(sm,0);
    s[d][mid] = a[mid];
    p[d][mid] = a[mid];
    
    for(int i = mid-1; i >= l; --i) {
        sm += a[i], prep += a[i];
        s[d][i] = Max(s[d][i+1],prep);
        p[d][i] = Max(p[d][i+1],sm);
        sm = Max(sm,0);
    }
    
    p[d][mid+1] = s[d][mid+1] = a[mid+1];
    prep = sm = a[mid+1], sm = Max(0,sm);
    for(int i = mid+2; i <= r; ++i) {
        sm += a[i], prep += a[i];
        s[d][i] = Max(s[d][i-1],prep);
        p[d][i] = Max(p[d][i-1],sm);
        sm = Max(sm,0);
    }
    
    build(ls, l, mid, d+1);
    build(rs, mid+1, r, d+1);
}

int query(int l, int r) {
    if(l == r) return a[l];
    int d = lg[pos[l]] - lg[pos[l]^pos[r]];
    return Max(Max(p[d][l],p[d][r]), s[d][l]+s[d][r]);
}

int main() {
    int n = read();
    for(int i = 1; i <= n; ++i)
        a[i] = read();
    int len = 2;
    while(len<n) len <<= 1;
    build(1, 1, len, 1);
    
    int l = len << 1;
    for(int i = 2; i <= l; ++i)
        lg[i] = lg[i>>1] + 1;
    
    
    int m = read();
    for(int i = 1, l, r; i <= m; ++i)
        l = read(), r = read(),
        cout << query(l, r) << '\n';

    return 0;
}

 

Aya

 

转载于:https://www.cnblogs.com/tztqwq/p/10720044.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值