首页 > 技术文章 > 黑科技:数组两倍空间线段树,实现方便

chy-2003 2019-11-07 20:32 原文

简介

某天膜 CaptainSlow 代码的时候发现了一个神奇的东西:

inline void Index(int L, int R) { return L + R | L != R; }

通过这个函数寻找线段树节点的下标只需要两倍空间!不动态开点也可以实现两倍空间!

(2021.7.14 更新了正确性证明)

正确性证明

先考虑前面一部分也就是 \(L+R\) 。画个图:

发现若所有区间长度为 \(1\) 或偶数,那么就没有问题了!安排的方式就是中序遍历。当区间长度为奇数时,再画个图:

就是说,奇数长度的线段下标按 \(L+R\) 就会和正中间那个叶子节点冲突。那么后面半个式子的作用就来了:

所有叶子节点的 \(L+R\) 都为偶数,不变;偶数长度的区间 \(L+R\) 都为奇数,不变;剩下奇数长度的区间下标加一。对于最后一种情况,由于右半侧下标和至少为 \(L+R+2\) ,所以加一不会有新的重复。

证毕。

luogu 3372

#include <cstdio>
#define Maxn 100010

long long T[Maxn << 1], Tag[Maxn << 1], A[Maxn];
int n, q;

inline int Index(int Left, int Right);
inline void Build(int Left, int Right);
inline void Push(int Left, int Right);
inline void Change(int Left, int Right, int L, int R, long long k);
inline long long Query(int Left, int Right, int L, int R);

int main() {
    scanf("%d%d", &n, &q);
    for (int i = 1; i <= n; ++i) scanf("%lld", &A[i]);
    Build(1, n);
    for (int i = 1; i <= q; ++i) {
        int opt, x, y;
        scanf("%d%d%d", &opt, &x, &y);
        if (opt == 1) {
            long long k;
            scanf("%lld", &k);
            Change(1, n, x, y, k);
        } else {
            printf("%lld\n", Query(1, n, x, y));
        }
    }
    return 0;
}

inline int Index(int Left, int Right) {
    return Left + Right | Left != Right;
}

inline void Build(int Left, int Right) {
    if (Left == Right) {
        T[Index(Left, Right)] = A[Left];
        return;
    }
    int Mid = (Left + Right) >> 1;
    Build(Left, Mid);
    Build(Mid + 1, Right);
    T[Index(Left, Right)] = T[Index(Left, Mid)] + T[Index(Mid + 1, Right)];
    return;
}

inline void Push(int Left, int Right) {
    if (Left == Right) return;
    int Mid = (Left + Right) >> 1;
    Tag[Index(Left, Mid)] += Tag[Index(Left, Right)];
    Tag[Index(Mid + 1, Right)] += Tag[Index(Left, Right)];
    T[Index(Left, Mid)] += Tag[Index(Left, Right)] * (Mid - Left + 1);
    T[Index(Mid + 1, Right)] += Tag[Index(Left, Right)] * (Right - Mid);
    Tag[Index(Left, Right)] = 0;
    return;
}

inline void Change(int Left, int Right, int L, int R, long long k) {
    Push(Left, Right);
    if (L <= Left && Right <= R) {
        Tag[Index(Left, Right)] += k;
        T[Index(Left, Right)] += (Right - Left + 1) * k;
        return;
    }
    int Mid = (Left + Right) >> 1;
    if (L <= Mid) Change(Left, Mid, L, R, k);
    if (R > Mid) Change(Mid + 1, Right, L, R, k);
    T[Index(Left, Right)] = T[Index(Left, Mid)] + T[Index(Mid + 1, Right)];
    return;
}

inline long long Query(int Left, int Right, int L, int R) {
    Push(Left, Right);
    if (L <= Left && Right <= R) return T[Index(Left, Right)];
    int Mid = (Left + Right) >> 1;
    if (R <= Mid) return Query(Left, Mid, L, R);
    if (L > Mid) return Query(Mid + 1, Right, L, R);
    return Query(Left, Mid, L, R) + Query(Mid + 1, Right, L, R);
}

推荐阅读