1 条题解

  • 0
    @ 2025-10-24 9:59:16

    「Konewka」题解:

    题目概述

    这是一个交互式问题,我们需要维护一个花园中的 nn 棵树,支持两种操作:

    1. 浇水操作:为区间 [a,b][a, b] 内的所有树高度增加 11
    2. 查询操作:查询区间 [a,b][a, b] 内高度至少为 kk 的成熟树的数量

    数据范围

    • 1n300,0001 \leq n \leq 300,000
    • 1k1091 \leq k \leq 10^9
    • 总操作次数 300,000\leq 300,000

    问题分析

    直接模拟的局限性

    最直接的想法是维护一个数组记录每棵树的高度,但这样:

    • 每次浇水需要 O(n)O(n) 时间
    • 每次查询需要 O(n)O(n) 时间
    • 总复杂度 O(n×q)O(n \times q),无法通过大数据

    关键观察

    我们不需要知道每棵树的精确高度,只需要知道:

    1. 哪些树已经成熟
    2. 未成熟的树还需要多少次浇水才能成熟

    高效解决方案

    数据结构设计

    1. 树状数组 (Fenwick Tree)

    作用:快速统计区间内成熟树的数量

    struct Fenwick {
        vector<int> bit;
        void add(int x);          // 标记第x棵树为成熟
        int query(int l, int r);  // 查询区间[l, r]内成熟树数量
    };
    

    复杂度

    • 更新:O(logn)O(\log n)
    • 查询:O(logn)O(\log n)

    2. 线段树 (Segment Tree)

    作用:维护未成熟树的最大高度,支持区间浇水

    struct SegmentTree {
        struct Node {
            int max_val, id, lazy;  // 最大值、位置、懒标记
        };
        vector<Node> tree;
        
        void update(int l, int r);     // 区间浇水
        void remove(int pos);          // 移除成熟树
        pair<int,int> query_max(int l, int r); // 查询区间最大值
    };
    

    核心思想

    • 维护每个区间内未成熟树的最大高度
    • 浇水时区间加1,如果最大值达到 kk,则标记该树为成熟

    算法流程

    初始化 inicjuj(n, k, D)

    void inicjuj(int N, int K, int *D) {
        n = N; k = K;
        fenwick = new Fenwick(n);
        seg_tree = new SegmentTree(n, D);
        
        // 检查初始就成熟的树
        for (int i = 0; i < n; i++) {
            if (D[i] >= k) {
                fenwick->add(i + 1);           // 标记为成熟
                seg_tree->remove(i);           // 从线段树中移除
            }
        }
    }
    

    浇水操作 podlej(a, b)

    void podlej(int a, int b) {
        seg_tree->update(a, b);  // 区间浇水
        
        // 检查是否有树新成熟
        while (true) {
            auto [max_val, id] = seg_tree->query_max(a, b);
            if (max_val >= k) {
                fenwick->add(id + 1);    // 标记为成熟
                seg_tree->remove(id);    // 从线段树中移除
            } else {
                break;  // 没有更多树成熟
            }
        }
    }
    

    查询操作 dojrzale(a, b)

    int dojrzale(int a, int b) {
        return fenwick->query(a + 1, b + 1);  // 直接查询成熟树数量
    }
    

    复杂度分析

    • 空间复杂度O(n)O(n)
    • 时间复杂度
      • 初始化:O(n)O(n)
      • 浇水操作:均摊 O(logn)O(\log n)(每棵树最多成熟一次)
      • 查询操作:O(logn)O(\log n)

    均摊分析:由于每棵树最多被标记为成熟一次,所有浇水操作中处理成熟树的总时间是 O(nlogn)O(n \log n)

    关键技巧与优化

    1. 懒标记线段树

    void push_down(int idx, int l, int r) {
        if (tree[idx].lazy != 0) {
            if (l != r) {
                apply(idx * 2, tree[idx].lazy);
                apply(idx * 2 + 1, tree[idx].lazy);
            }
            tree[idx].lazy = 0;
        }
    }
    

    确保区间更新在 O(logn)O(\log n) 时间内完成。

    2. 最大值维护与位置记录

    void push_up(int idx, int l, int r) {
        if (tree[left].max_val > tree[right].max_val) {
            tree[idx].max_val = tree[left].max_val;
            tree[idx].id = tree[left].id;
        } else {
            tree[idx].max_val = tree[right].max_val;
            tree[idx].id = tree[right].id;
        }
    }
    

    不仅记录最大值,还记录最大值的位置,便于快速找到成熟的树。

    3. 成熟树的惰性删除

    void remove(int pos) {
        tree[pos].max_val = -INF;  // 设为负无穷,不再参与计算
    }
    

    将成熟树的高度设为负无穷,这样它就不会影响后续的最大值查询。

    示例演示

    考虑样例:

    • n=4,k=6n = 4, k = 6
    • 初始高度:[5,4,3,7][5, 4, 3, 7]

    执行过程

    1. 初始化:树3高度7≥6,标记为成熟
    2. 查询[2,3]:返回1(只有树3成熟)
    3. 浇水[0,2]:树0,1,2高度+1 → [6,5,4,7]
      • 树0高度达到6,标记为成熟
    4. 查询[1,2]:返回0(没有成熟树)
    5. 继续浇水操作...

    总结

    本题的解决方案展示了如何通过巧妙的数据结构组合解决复杂的区间更新查询问题:

    1. 分离关注点:用不同数据结构分别处理成熟树和未成熟树
    2. 最大值监控:通过线段树快速找到可能新成熟的树
    3. 惰性操作:避免不必要的计算,提高效率
    4. 均摊分析:确保总体复杂度在可接受范围内

    这种"最大值线段树 + 统计树状数组"的思路可以推广到其他需要监控阈值和统计数量的区间操作问题中。

    • 1

    信息

    ID
    3982
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    4
    已通过
    1
    上传者