📜  分段树中的延迟传播(1)

📅  最后修改于: 2023-12-03 15:07:09.538000             🧑  作者: Mango

分段树中的延迟传播

分段树(Segment Tree)是一种常见的数据结构,用于解决区间查询问题,例如区间最值、区间和等等。而在分段树中,为了提高查询的效率,我们通常需要进行一些预处理,其中一个常见的预处理方法就是延迟传播。

延迟传播是什么?

在分段树中,为了避免重复计算,我们通常使用线段树的思想,将区间一分为二,然后将左右两个子区间的计算结果合并得到父区间的计算结果。但是,在进行区间修改时,可能会导致子区间的计算结果失效,因此我们需要对修改操作进行优化,而延迟传播就是一种优化方法。

延迟传播的核心思想是,当进行区间修改操作时,不直接修改子区间的值,而是标记该区间需要进行修改,并将修改操作延迟到该区间被查询时再进行。

如何实现延迟传播?

在实现延迟传播时,我们需要在节点中添加一个标记(通常称为lazyTag),用来表示该节点需要进行的修改操作。在每次进行区间合并操作时,我们需要将左右子节点的lazyTag进行合并,然后根据lazyTag对节点进行更新。在查询节点的值时,如果该节点的lazyTag不为空,则需要将lazyTag更新到子节点,并将lazyTag重置为null。

/**
 * 包含 LazyTag 的线段树节点类
 */
class SegmentTreeNode {
    int start;
    int end;
    int sum;
    int lazyTag;

    public SegmentTreeNode(int start, int end) {
        this.start = start;
        this.end = end;
    }
}

/**
 * 分段树类,包含延迟传播操作
 */
class SegmentTree {
    private SegmentTreeNode[] tree;

    public SegmentTree(int[] nums) {
        tree = new SegmentTreeNode[nums.length * 4];
        buildTree(0, 0, nums.length - 1, nums);
    }

    private void buildTree(int index, int start, int end, int[] nums) {
        tree[index] = new SegmentTreeNode(start, end);
        if (start == end) {
            tree[index].sum = nums[start];
            return;
        }
        int mid = start + (end - start) / 2;
        buildTree(index * 2 + 1, start, mid, nums);
        buildTree(index * 2 + 2, mid + 1, end, nums);
        tree[index].sum = tree[index * 2 + 1].sum + tree[index * 2 + 2].sum;
    }

    public void updateRange(int start, int end, int val) {
        updateRangeHelper(0, start, end, val);
    }

    private void updateRangeHelper(int index, int start, int end, int val) {
        if (tree[index].start >= start && tree[index].end <= end) {
            tree[index].lazyTag += val;
            tree[index].sum += (tree[index].end - tree[index].start + 1) * val;
            return;
        }
        pushDown(index);
        int mid = tree[index].start + (tree[index].end - tree[index].start) / 2;
        if (start <= mid) {
            updateRangeHelper(index * 2 + 1, start, end, val);
        }
        if (end > mid) {
            updateRangeHelper(index * 2 + 2, start, end, val);
        }
        tree[index].sum = tree[index * 2 + 1].sum + tree[index * 2 + 2].sum;
    }

    public int queryRange(int start, int end) {
        return queryRangeHelper(0, start, end);
    }

    private int queryRangeHelper(int index, int start, int end) {
        if (tree[index].start >= start && tree[index].end <= end) {
            return tree[index].sum;
        }
        pushDown(index);
        int mid = tree[index].start + (tree[index].end - tree[index].start) / 2;
        int sum = 0;
        if (start <= mid) {
            sum += queryRangeHelper(index * 2 + 1, start, end);
        }
        if (end > mid) {
            sum += queryRangeHelper(index * 2 + 2, start, end);
        }
        return sum;
    }

    private void pushDown(int index) {
        if (tree[index].lazyTag != 0) {
            int leftIndex = index * 2 + 1;
            int rightIndex = index * 2 + 2;
            tree[leftIndex].lazyTag += tree[index].lazyTag;
            tree[leftIndex].sum += (tree[leftIndex].end - tree[leftIndex].start + 1) * tree[index].lazyTag;
            tree[rightIndex].lazyTag += tree[index].lazyTag;
            tree[rightIndex].sum += (tree[rightIndex].end - tree[rightIndex].start + 1) * tree[index].lazyTag;
            tree[index].lazyTag = 0;
        }
    }
}

在上述代码中,我们实现了分段树的查询和更新操作,并采用lazyTag机制进行延迟传播。

总结

延迟传播是一种优化分段树的常用机制,在进行区间修改操作时,采用lazyTag机制对修改操作进行标记,并延迟执行操作,可以大大提高分段树的更新效率。同时,在进行查询操作时,需要注意将lazyTag更新到下一层子节点,以保证查询结果的正确性。