P1502 窗口的星星 扫描线

代码注释与解题思路

解题思路

这道题目需要使用扫描线算法结合线段树来解决,属于经典的矩形覆盖最大值问题。具体思路如下:

  1. 问题转化:将每个星星转化为一个矩形区域,表示当窗口的右上角位于这个矩形内时,该星星会被包含在窗口中。

  2. 扫描线处理:使用扫描线算法处理这些矩形,从左到右扫描,遇到矩形的左边界时增加亮度值,遇到右边界时减少亮度值。

  3. 线段树维护:使用线段树来维护当前扫描线上各点的亮度总和,并快速查询最大值。

  4. 离散化处理:由于坐标范围很大,需要对y坐标进行离散化处理,以减少线段树的空间需求。

代码注释

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

const int N = 2e5 + 10;

// 定义扫描线结构体
struct Line{ 
    ll x, y1, y2;  // x坐标,y的上下边界
    ll f;          // 权值(+l表示添加,-l表示删除)
};

Line line[N];      // 存储所有扫描线
struct node{
    ll val, lazy;  // 线段树节点:区间最大值和懒惰标记
};

node t[N << 3];    // 线段树数组(开8倍空间)
ll ys[N << 1], cnty; // ys用于离散化y坐标,cnty记录离散化后的y坐标数量
ll n, w, h, m;      // n:星星数量,w:窗口宽,h:窗口高,m:离散化后的y坐标区间数

// 扫描线排序比较函数
bool cmp(Line a, Line b){
    if(a.x == b.x) return a.f > b.f; // x相同,添加操作优先于删除操作
    return a.x < b.x;                // 否则按x从小到大排序
}

// 线段树下推懒惰标记
void down(int rt, int l, int r, ll z){
    t[rt].val += z;
    t[rt].lazy += z;
}

// 下推懒惰标记
void pushdown(int rt, int l, int r){
    if(t[rt].lazy == 0) return;
    int mid = (l + r) >> 1;
    down(lson, t[rt].lazy);
    down(rson, t[rt].lazy);
    t[rt].lazy = 0;
}

// 上推更新区间最大值
void pushup(int rt){
    t[rt].val = max(t[lc].val, t[rc].val);
}

// 构建线段树
void build(int rt, int l, int r){
    t[rt] = {0, 0}; // 初始化节点
    if(l == r) return;
    int mid = (l + r) >> 1;
    build(lson);
    build(rson);
}

// 线段树区间更新
void change(int rt, int l, int r, int x, int y, ll z){
    if(r < x || y < l) return; // 完全不在区间内
    if(x <= l && r <= y){      // 完全包含在区间内
        t[rt].val += z;
        t[rt].lazy += z;
        return;
    }
    pushdown(rt, l, r);        // 下推懒惰标记
    int mid = (l + r) >> 1;
    change(lson, x, y, z);     // 更新左子树
    change(rson, x, y, z);     // 更新右子树
    pushup(rt);                // 上推更新
}

// 处理每组数据
void solve(){
    cnty = 0;
    cin >> n >> w >> h;
    for(int i = 1; i <= n; i++){
        ll x, y, l; 
        scanf("%lld%lld%lld", &x, &y, &l);
        // 将每个星星转化为两条扫描线:左边界(添加)和右边界(删除)
        line[2 * i - 1] = {x, y, y + h - 1, l};
        line[2 * i] = {x + w - 1, y, y + h - 1, -l};
        // 记录y坐标用于离散化
        ys[++cnty] = y;
        ys[++cnty] = y + h - 1;
    } 
    
    // 扫描线排序
    sort(line + 1, line + 1 + 2 * n, cmp);
    // y坐标离散化
    sort(ys + 1, ys + 1 + cnty);
    cnty = unique(ys + 1, ys + 1 + cnty) - ys - 1;
    
    // 构建线段树(离散化后的y坐标区间为[1, m])
    m = cnty - 1;
    build(1, 1, m);
    
    ll ans = 0;
    // 处理每条扫描线
    for(int i = 1; i <= 2 * n; i++){
        // 获取离散化后的y坐标区间
        int y1 = lower_bound(ys + 1, ys + 1 + cnty, line[i].y1) - ys;
        int y2 = lower_bound(ys + 1, ys + 1 + cnty, line[i].y2) - ys;
        // 更新线段树区间
        change(1, 1, m, y1, y2, line[i].f);
        // 更新答案(当前扫描线上最大值)
        ans = max(ans, t[1].val);
    }
    cout << ans << endl;
}

int main(){
    int T; 
    cin >> T;
    while(T--){
        solve();
    }
    return 0;
}

关键点说明

  1. 扫描线处理:每个星星转换为一个矩形区域,左边界添加亮度,右边界减去亮度。

  2. 离散化处理:由于坐标范围很大,必须对y坐标进行离散化,否则线段树会超出内存限制。

  3. 线段树维护:线段树用于维护当前扫描线上各点的亮度总和,并支持区间更新和最大值查询。

  4. 排序规则:当x坐标相同时,添加操作优先于删除操作,确保正确计算边界情况。

  5. 边界处理:窗口的边框不算在内,因此使用x + w - 1y + h - 1作为右边界和下边界。

这个算法的时间复杂度为O(n log n),适用于题目给定的数据范围。

posted @ 2025-05-22 13:57  CRt0729  阅读(26)  评论(0)    收藏  举报