洛谷P3373 线段树模板2 Java

本文介绍了一种数据结构——线段树,并详细讲解了如何实现带懒惰传播的线段树,包括区间加法、乘法更新及查询操作。通过具体的代码实现,帮助读者理解线段树在区间更新问题上的高效应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接

import java.io.*;
class Node {
    int l; // 区间左端点
    int r; // 区间右端点
    long sum; // 区间和
    long add; // 加法标记
    long mul; // 乘法标记

    public Node(int l, int r, long sum, long add, long mul) {
        this.l = l;
        this.r = r;
        this.sum = sum;
        this.add = add;
        this.mul = mul;
    }
}

public class Main {

    static int n; // 数据范围 [1, n]
    static int p; // 取模
    static int[] array; // 原始数组
    static Node[] tree; // 线段树

    // 线段数树初始化
    static void init() {
        tree = new Node[n * 4];
        for (int i = 1; i < tree.length; i++) {
            tree[i] = new Node(0, 0, 0, 0, 1);
        }
    }

    // 建树,用回溯从下往上建
    static void build(int l, int r, int num) {
        tree[num].l = l;
        tree[num].r = r;
        // 如果区间长度为 1,递归结点
        if (l == r) {
            tree[num].sum = array[l] % p;
            return;
        }
        int mid = (l + r) / 2;
        build(l, mid, num * 2);
        build(mid + 1, r, num * 2 + 1);
        update(num); // 记得更新结点的值
    }

    // 更新结点的值
    static void update(int num) {
        tree[num].sum = (tree[num * 2].sum + tree[num * 2 + 1].sum) % p;
    }

    // 区间修改,区间类型有三种情况
    // 将区间[l, r]每个都加上 value
    static void modifyAdd(int l, int r, int num, int value) {
        // 如果找到要修改的区间
        if (tree[num].l == l && tree[num].r == r) {
            tree[num].add += value;
            tree[num].sum = (tree[num].sum + (r - l + 1) * value) % p; // 添加懒惰标记,暂时不需要给儿子结点修改值
            return;
        }
        // 注意:修改区间也要 pushDown
        if (tree[num].add != 0 || tree[num].mul != 1) {
            pushDown(num);
        }

        int mid = (tree[num].l + tree[num].r) / 2;
        // 修改的区间都包含在左子树里
        if (r <= mid) {
            modifyAdd(l, r, num * 2, value);
        }
        // 修改的区间都包含在右子树里
        else if (l > mid) {
            modifyAdd(l, r, num * 2 + 1, value);
        }
        // 将要修改的区间拆开
        else {
            modifyAdd(l, mid, num * 2, value);
            modifyAdd(mid + 1, r, num * 2 + 1, value);
        }
        update(num);
    }

    // 将区间[l, r]每个都乘上 value
    static void modifyMul(int l, int r, int num, int value) {
        // 如果找到要修改的区间
        if (tree[num].l == l && tree[num].r == r) {
            tree[num].mul = tree[num].mul * value % p;
            tree[num].add = tree[num].add * value % p;  // 注意:这里要乘,把乘法标记的作用加到加法标记
            tree[num].sum = tree[num].sum * value % p;
            return;
        }
        // 注意:修改区间也要 pushDown
        if (tree[num].add != 0 || tree[num].mul != 1) {
            pushDown(num);
        }

        int mid = (tree[num].l + tree[num].r) / 2;
        // 修改的区间都包含在左子树里
        if (r <= mid) {
            modifyMul(l, r, num * 2, value);
        }
        // 修改的区间都包含在右子树里
        else if (l > mid) {
            modifyMul(l, r, num * 2 + 1, value);
        }
        // 将要修改的区间拆开
        else {
            modifyMul(l, mid, num * 2, value);
            modifyMul(mid + 1, r, num * 2 + 1, value);
        }
        update(num);
    }

    // 用于将懒惰标记下传给儿子们,然后更新儿子们的值,清空当前结点的懒惰标记
    // 当前结点的懒惰标记是对儿子结点的值起作用的,对自己是无关的
    static void pushDown(int num) {
        // 如果是叶子结点
        if (tree[num].l == tree[num].r) {
            tree[num].add = 0;
            tree[num].mul = 1;
            return;
        }
        // 先乘后加
        // 下传乘法标记
        tree[num * 2].mul = tree[num * 2].mul * tree[num].mul % p;
        tree[num * 2 + 1].mul = tree[num * 2 + 1].mul * tree[num].mul % p;

        // 下传加法标记,父亲的乘法标记对儿子的加法标记也是有作用的
        tree[num * 2].add = tree[num * 2].add * tree[num].mul % p;
        tree[num * 2 + 1].add = tree[num * 2 + 1].add * tree[num].mul % p;
        tree[num * 2].add += tree[num].add;
        tree[num * 2 + 1].add += tree[num].add;

        // 更新儿子的值
        tree[num * 2].sum = tree[num * 2].sum * tree[num].mul % p;
        tree[num * 2].sum = (tree[num * 2].sum + (tree[num * 2].r - tree[num * 2].l + 1) * tree[num].add) % p;
        tree[num * 2 + 1].sum = tree[num * 2 + 1].sum * tree[num].mul % p;
        tree[num * 2 + 1].sum = (tree[num * 2 + 1].sum + (tree[num * 2 + 1].r - tree[num * 2 + 1].l + 1) * tree[num].add) % p;

        tree[num].mul = 1;
        tree[num].add = 0;
    }

    // 区间查询
    static long query(int l, int r, int num) {
        if (tree[num].add != 0 || tree[num].mul != 1) {
            pushDown(num);
        }
        if (tree[num].l == l && tree[num].r == r) {
            return tree[num].sum;
        }
        int mid = (tree[num].l + tree[num].r) / 2;
        if (r <= mid) {
            return query(l, r, num * 2);
        }
        if (l > mid) {
            return query(l, r, num * 2 + 1);
        }
        return query(l, mid, num * 2) + query(mid + 1, r, num * 2 + 1);
    }

    public static void main(String[] args) throws IOException {

        n = nextInt();
        int m = nextInt(); // 总操作个数
        p = nextInt(); // 模数
        array = new int[n + 1];
        for (int i = 1; i <= n; i++) {
            array[i] = nextInt();
        }

        // 开始建树
        init();
        build(1, n, 1);

        // 开始查询
        for (int i = 0; i < m; i++) {
            int oper = nextInt();
            int x = nextInt();
            int y = nextInt();
            if (oper == 1) {
                int k = nextInt();
                modifyMul(x, y, 1, k);
            }
            else if (oper == 2) {
                int k = nextInt();
                modifyAdd(x, y, 1, k);
//                System.out.println("******************");
//                for (int j = 1; j <= 7; j++) {
//                    System.out.println(tree[j].l + " " + tree[j].r + " " + tree[j].sum + " " + tree[j].lazy);
//                }
//                System.out.println("*******************");
            }
            else if (oper == 3) {
                long ans = query(x, y, 1) % p;
                pw.println(ans);
            }
        }
        pw.flush();
    }

    static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    static StreamTokenizer st = new StreamTokenizer(br);
    static PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
    static int nextInt() throws IOException {
        st.nextToken();
        return (int) st.nval;
    }
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值