P1502 窗口的星星 扫描线
代码注释与解题思路
解题思路
这道题目需要使用扫描线算法结合线段树来解决,属于经典的矩形覆盖最大值问题。具体思路如下:
-
问题转化:将每个星星转化为一个矩形区域,表示当窗口的右上角位于这个矩形内时,该星星会被包含在窗口中。
-
扫描线处理:使用扫描线算法处理这些矩形,从左到右扫描,遇到矩形的左边界时增加亮度值,遇到右边界时减少亮度值。
-
线段树维护:使用线段树来维护当前扫描线上各点的亮度总和,并快速查询最大值。
-
离散化处理:由于坐标范围很大,需要对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; }
关键点说明
-
扫描线处理:每个星星转换为一个矩形区域,左边界添加亮度,右边界减去亮度。
-
离散化处理:由于坐标范围很大,必须对y坐标进行离散化,否则线段树会超出内存限制。
-
线段树维护:线段树用于维护当前扫描线上各点的亮度总和,并支持区间更新和最大值查询。
-
排序规则:当x坐标相同时,添加操作优先于删除操作,确保正确计算边界情况。
-
边界处理:窗口的边框不算在内,因此使用
x + w - 1和y + h - 1作为右边界和下边界。
这个算法的时间复杂度为O(n log n),适用于题目给定的数据范围。

浙公网安备 33010602011771号