1 条题解

  • 0
    @ 2025-10-28 8:53:08

    好的,我来为这道题写一个详细的题解。

    题目理解

    我们有一个长度为 nn0101 序列,初始全为 11(没有脑洞)。支持三种操作:

    1. 挖脑洞:将区间 [l,r][l, r] 全部设为 00
    2. 脑洞治疗:用区间 [l0,r0][l_0, r_0] 中的 11(正常组织)去填补区间 [l1,r1][l_1, r_1] 中的 00(脑洞)
    3. 查询:询问区间 [l,r][l, r] 中最长的连续 00 的长度

    问题分析

    1. 操作特点

    • 操作 0:区间赋值为 00 → 区间覆盖操作
    • 操作 2:查询最长连续 00 → 区间信息维护
    • 操作 1:最复杂,需要:
      1. 统计 [l0,r0][l_0, r_0] 中有多少个 11
      2. [l0,r0][l_0, r_0] 全部设为 00
      3. 用这些 11 去填补 [l1,r1][l_1, r_1] 中的脑洞(从左到右填)

    数据结构选择

    由于 n,m200000n, m \leq 200000,我们需要 O(logn)O(\log n)O(log2n)O(\log^2 n) 复杂度的算法。

    线段树是合适的选择,需要维护:

    对于每个区间,存储:

    • sumsum11 的数量
    • lenlen:区间长度
    • lmax0,rmax0,max0lmax0, rmax0, max0:从左开始的最长连续 00,从右开始的最长连续 00,整个区间的最长连续 00
    • lmax1,rmax1,max1lmax1, rmax1, max1:同理对于 11
    • lazylazy:懒标记(1-1 无操作,00 全设 0011 全设 11

    线段树节点设计

    struct Node {
        int sum;        // 1的数量
        int len;        // 区间长度
        
        // 对于0的连续信息
        int lmax0, rmax0, max0;
        // 对于1的连续信息  
        int lmax1, rmax1, max1;
        
        int lazy;       // -1:无, 0:全0, 1:全1
    };
    

    信息合并

    合并两个区间 [l][l][r][r]

    void merge(Node &res, const Node &l, const Node &r) {
        res.sum = l.sum + r.sum;
        res.len = l.len + r.len;
        
        // 合并0的连续信息
        res.lmax0 = (l.lmax0 == l.len) ? l.len + r.lmax0 : l.lmax0;
        res.rmax0 = (r.rmax0 == r.len) ? r.len + l.rmax0 : r.rmax0;
        res.max0 = max({l.max0, r.max0, l.rmax0 + r.lmax0});
        
        // 合并1的连续信息
        res.lmax1 = (l.lmax1 == l.len) ? l.len + r.lmax1 : l.lmax1;
        res.rmax1 = (r.rmax1 == r.len) ? r.len + l.rmax1 : r.rmax1;
        res.max1 = max({l.max1, r.max1, l.rmax1 + r.lmax1});
    }
    

    操作实现

    1. 挖脑洞(操作 0)

    区间覆盖为 00

    void update_range0(int u, int l, int r, int ql, int qr) {
        if (ql <= l && r <= qr) {
            apply_tag(u, 0);  // 全设为0
            return;
        }
        push_down(u);
        int mid = (l + r) >> 1;
        if (ql <= mid) update_range0(u<<1, l, mid, ql, qr);
        if (qr > mid) update_range0(u<<1|1, mid+1, r, ql, qr);
        push_up(u);
    }
    

    2. 查询最长连续0(操作 2)

    Node query_max0(int u, int l, int r, int ql, int qr) {
        if (ql <= l && r <= qr) return tree[u];
        push_down(u);
        int mid = (l + r) >> 1;
        if (qr <= mid) return query_max0(u<<1, l, mid, ql, qr);
        if (ql > mid) return query_max0(u<<1|1, mid+1, r, ql, qr);
        
        Node left = query_max0(u<<1, l, mid, ql, qr);
        Node right = query_max0(u<<1|1, mid+1, r, ql, qr);
        Node res;
        merge(res, left, right);
        return res;
    }
    

    3. 脑洞治疗(操作 1)

    这是最复杂的操作,分三步:

    void treatment(int l0, int r0, int l1, int r1) {
        // 1. 统计[l0, r0]中1的数量
        int cnt1 = query_sum(1, 1, n, l0, r0);
        
        // 2. 将[l0, r0]全部设为0
        update_range0(1, 1, n, l0, r0);
        
        if (cnt1 == 0) return;  // 没有可用组织
        
        // 3. 在[l1, r1]中填补cnt1个1(从左到右)
        fill_ones(1, 1, n, l1, r1, cnt1);
    }
    

    关键操作:fill_ones

    在区间 [l,r][l, r] 中从左到右填充 cntcnt11

    int fill_ones(int u, int l, int r, int ql, int qr, int cnt) {
        if (cnt == 0) return 0;
        if (ql <= l && r <= qr) {
            int zeros = (r - l + 1) - tree[u].sum;  // 当前区间0的数量
            if (zeros <= cnt) {
                // 可以填满整个区间
                apply_tag(u, 1);
                return zeros;
            }
        }
        push_down(u);
        int mid = (l + r) >> 1;
        int filled = 0;
        
        // 先填左子区间
        if (ql <= mid) {
            filled += fill_ones(u<<1, l, mid, ql, qr, cnt);
            cnt -= filled;
        }
        // 再填右子区间
        if (cnt > 0 && qr > mid) {
            filled += fill_ones(u<<1|1, mid+1, r, ql, qr, cnt);
        }
        push_up(u);
        return filled;
    }
    

    复杂度分析

    • 操作 0:区间覆盖,O(logn)O(\log n)
    • 操作 2:区间查询,O(logn)O(\log n)
    • 操作 1:统计 O(logn)O(\log n) + 覆盖 O(logn)O(\log n) + 填充 O(log2n)O(\log^2 n)
    • 总复杂度:O(mlog2n)O(m \log^2 n),对于 n,m200000n, m \leq 200000 可接受

    样例验证

    输入:

    10 10
    0 2 2
    0 4 6  
    0 10 10
    2 1 10
    1 8 10 1 4
    2 1 10
    1 1 4 8 10
    2 1 10
    1 7 10 1 6
    2 1 10
    

    逐步分析:

    1. 初始:全1 [1,1,1,1,1,1,1,1,1,1]
    2. 挖2-2:[1,0,1,1,1,1,1,1,1,1]
    3. 挖4-6:[1,0,1,0,0,0,1,1,1,1]
    4. 挖10-10:[1,0,1,0,0,0,1,1,1,0]
    5. 查询1-10:最长连续0是4-6,长度3 → 输出3
    6. 治疗:用8-10修补1-4
      • 8-10有2个1(位置8,9)
      • 填补1-4:位置1,3已为1,填补2,4 → [1,1,1,1,0,0,1,0,0,0]
    7. 查询1-10:最长连续0是8-10,长度3 → 输出3
    8. 治疗:用1-4修补8-10
      • 1-4有4个1
      • 填补8-10:全部填补 → [0,0,0,0,0,0,1,1,1,1]
    9. 查询1-10:最长连续0是1-6,长度6 → 输出6
    10. 治疗:用7-10修补1-6
      • 7-10有4个1
      • 填补1-6:只能填前4个 → [1,1,1,1,0,0,0,0,0,0]
    11. 查询1-10:最长连续0是5-10,长度6 → 输出6

    输出:3 3 6 6


    总结

    本题的关键在于:

    1. 设计合适的线段树节点,维护连续0和连续1的信息
    2. 实现复杂的大脑治疗操作,特别是从左到右的填充逻辑
    3. 注意懒标记的下传和信息的正确合并
    4. 处理边界情况和效率优化
    • 1

    信息

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