P2184 贪婪大陆 线段树差分

解题思路与代码注释

题目理解

这道题目需要我们维护一个战壕的地雷布置情况,支持两种操作:

  1. 布雷操作:在区间[l,r]布置一种新型地雷

  2. 查询操作:查询区间[l,r]内有多少种不同的地雷

关键点在于如何高效统计一个查询区间内覆盖的不同地雷种类数。

解题方法

使用两棵线段树分别维护:

  1. st树:记录所有布雷操作的起始位置

  2. ed树:记录所有布雷操作的结束位置

对于查询区间[L,R]

  • 结果 = 在R之前开始的布雷数 - 在L-1之前结束的布雷数

  • 这样计算可以确保只统计那些与查询区间有交集的地雷

代码详细注释

#include<bits/stdc++.h>
#define lc rt << 1      // 左子节点索引
#define rc rt << 1 | 1  // 右子节点索引
#define lson lc,l,mid   // 左子树参数:左子节点,区间[l,mid]
#define rson rc,mid + 1,r // 右子树参数:右子节点,区间[mid+1,r]
#define ll long long 
using namespace std;

const int N = 5e5 + 10, inf = 0x3f3f3f3f;

// 线段树节点结构体
struct node{
    int sum[N << 2];  // 线段树数组,存储区间和
    
    // 更新父节点的值
    void pushup(int rt){
        sum[rt] = sum[lc] + sum[rc];  // 父节点的和等于左右子节点和相加
    }

    // 单点修改函数
    void change(int rt,int l,int r,int x,int val) {
        if(r < x || x < l) return;  // 超出修改范围
        if(l == r){  // 到达叶子节点
            sum[rt] += val;  // 修改该位置的值
            return;
        }
        int mid = (l + r) >> 1;
        change(lson,x,val);  // 修改左子树
        change(rson,x,val);  // 修改右子树
        pushup(rt);          // 更新父节点
    }
    
    // 区间查询函数
    int query(int rt,int l,int r,int x,int y) {
        if(r < x || y < l) return 0;  // 区间无交集返回0
        if(x <= l && r <= y) return sum[rt];  // 完全包含直接返回区间和
        int mid = (l + r) >> 1;
        // 返回左右子树查询结果的和
        return query(lson,x,y) + query(rson,x,y);
    }
};

node st, ed;  // 两棵线段树:st记录布雷起点,ed记录布雷终点
int n, m;     // n-防线长度,m-操作次数

int main() {
    cin >> n >> m;
    for(int i = 1; i <= m; i++) {
        int op, x, y;
        scanf("%d%d%d", &op, &x, &y);
        
        if(op == 1) {  // 布雷操作
            st.change(1,1,n,x,1);   // 在起点x处+1
            ed.change(1,1,n,y,1);   // 在终点y处+1
        }
        if(op == 2) {  // 查询操作
            // 查询结果 = 在y之前开始的布雷数 - 在x-1之前结束的布雷数
            printf("%d\n", st.query(1,1,n,1,y) - ed.query(1,1,n,1,x-1));
        }
    }
    return 0;
}

复杂度分析

  • 时间复杂度

    • 每次修改操作:O(log n)

    • 每次查询操作:O(log n)

    • 总复杂度:O(m log n)

  • 空间复杂度:O(n)

为什么这样设计?

  1. 数学原理

    • 一个布雷区间[L_i,R_i]会覆盖查询区间[L,R]当且仅当:

      • L_i ≤ R (布雷开始点在查询结束点之前)

      • R_i ≥ L (布雷结束点在查询开始点之后)

    • 因此覆盖数量 = 开始点≤R的数量 - 结束点<L的数量

  2. 线段树优势

    • 可以高效处理点更新和区间查询

    • 两棵独立的线段树分别维护开始和结束信息

    • 查询时只需两次区间查询和一次减法运算

这个解法能够高效处理题目给出的最大数据规模,保证在时间限制内完成所有操作。线段树的实现简洁高效,适合这类区间统计问题。

 

示例场景

假设战壕长度n=5,有以下操作序列:

  1. 在区间[1,3]布雷(类型1)

  2. 查询区间[2,5]的地雷种类数

  3. 在区间[2,4]布雷(类型2)

  4. 查询区间[3,5]的地雷种类数

操作步骤解析

初始状态

  • st树(记录布雷起点):全0

  • ed树(记录布雷终点):全0

操作1:在[1,3]布雷

  • st.change(1,1,5,1,1):在位置1+1

  • ed.change(1,1,5,3,1):在位置3+1

此时:

  • st树:位置1=1,其他=0

  • ed树:位置3=1,其他=0

操作2:查询[2,5]

计算:
st.query(1,1,5,1,5) - ed.query(1,1,5,1,1)

解释:

  1. st.query(1,1,5,1,5):查询所有起点≤5的布雷数=1(只有[1,3])

  2. ed.query(1,1,5,1,1):查询所有终点<2的布雷数=0
    结果:1-0=1

操作3:在[2,4]布雷

  • st.change(1,1,5,2,1):在位置2+1

  • ed.change(1,1,5,4,1):在位置4+1

此时:

  • st树:位置1=1,位置2=1

  • ed树:位置3=1,位置4=1

操作4:查询[3,5]

计算:
st.query(1,1,5,1,5) - ed.query(1,1,5,1,2)

解释:

  1. st.query(1,1,5,1,5):查询所有起点≤5的布雷数=2([1,3]和[2,4])

  2. ed.query(1,1,5,1,2):查询所有终点<3的布雷数=0
    结果:2-0=2

为什么这样计算?

关键理解:

  • st.query(1,1,n,1,y) 计算的是"所有在y位置之前开始的布雷数"

  • ed.query(1,1,n,1,x-1) 计算的是"所有在x位置之前就结束的布雷数"

  • 两者相减得到的是"与查询区间[x,y]有交集的布雷数"

数学证明

一个布雷区间[L_i,R_i]与查询区间[x,y]有交集的条件是:
L_i ≤ y 且 R_i ≥ x

所以:
总数 = 满足L_i ≤ y的布雷数 - 满足R_i < x的布雷数
= st.query(y) - ed.query(x-1)

可视化示例

初始战壕:[ , , , , ]
操作1:[1,3]布雷 → [1,1,1, , ]
操作2:查询[2,5] → 只有[1,3]覆盖 → 结果1
操作3:[2,4]布雷 → [1,2,2,1, ]
操作4:查询[3,5] → [1,3]和[2,4]都覆盖 → 结果2

这个例子清楚地展示了如何通过两棵线段树来高效统计区间覆盖的地雷种类数。

posted @ 2025-05-21 13:08  CRt0729  阅读(16)  评论(0)    收藏  举报