题目:
解题思路:
设某颗星星的坐标为
(
x
,
y
)
(x,y)
(x,y) ,那么当窗户的右上角端点的坐标在
(
x
∼
x
+
w
−
1
,
y
∼
y
+
h
−
1
)
(x\sim x+w-1 , y\sim y+h-1)
(x∼x+w−1,y∼y+h−1) 这个范围内时,星星就会出现在窗户里。(因为题目中说边界的星星不算,所以长和宽都要减1)
于是我们可以将每个星星都扩展成一个矩形,通过画图我们可以发现:若两个矩形之间有重叠部分,那么他们便可以放在同一个窗户中,那么这个窗户的亮度就是这两颗星星的亮度之和。
于是,我们可以将问题转化为:平面上有若干个矩形,每个矩形都带有一个权值,求在哪个坐标上权值的总和最大。
接下来我们就可以使用扫描线来解决这个问题了,若当前星星的亮度值为 k , 则矩形的入边的权值设为 k ,出边为 -k ,此时我们只要求扫描线上的区间最大值即可得出答案。
需要注意的几点细节:
1.星星的坐标范围为
0
≤
x
i
,
y
i
≤
2
31
0 \leq x_i,y_i \leq 2^{31}
0≤xi,yi≤231,所以我们需要将坐标进行离散化处理
2.在对扫描线排序时,首先按照 x 坐标进行升序排序;当x坐标重合时,按照 state 值升序排序。这样排序的原因是,当一个矩形的出边和另一个矩形的入边重合的时候,应该先减去出边的星星,因为边界值不算,然后再加上另一个星星的亮度。如果不这样的话,答案可能偏大。
3.创建扫描线的时候,入边是
(
x
,
y
,
y
+
h
−
1
,
k
)
({x, y, y+h-1, k})
(x,y,y+h−1,k) ,出边是
(
x
+
w
,
y
,
y
+
h
−
1
,
−
k
)
({x+w, y, y+h-1, -k})
(x+w,y,y+h−1,−k),出边的x坐标不是
x
+
w
−
1
x+w-1
x+w−1 ,因为每一条扫描线都应该做加减的操作,也就应该是窗户的边界,所以宽度不需要减1。但是高度需要减1,因为需要保证这个星星在窗户内。
4.之前计算矩形面积时,是对区间建立线段树,但是这题是对格点 y 坐标,所以,建树和询问时与之前不同。正因为此,延迟标记才可用于此题。
代码
#include <bits/stdc++.h>
#define ll long long
#define qc ios::sync_with_stdio(false); cin.tie(0);cout.tie(0)
#define fi first
#define se second
#define PII pair<int, int>
#define PLL pair<ll, ll>
#define pb push_back
using namespace std;
const int MAXN = 1e4 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const ll mod = 1e9 + 7;
int n,w,h;
ll ans;
struct line{
ll x, y1, y2;
ll state;
bool operator<(line b) const {
if(x == b.x)//按照星星的权值从小到大排序,因为要先减去右边界的星星,再加
return state < b.state;
return x < b.x;
}
};
ll ls[MAXN];//去重排序后的y坐标
ll alls[MAXN];//所有y坐标
int lscnt;
int ask(ll x){
return lower_bound(ls+1, ls+1+lscnt, x) - ls;
}
struct node1{
int l, r;//线段的编号
ll val;//该区间星星亮度的最大值
ll lazy;//延迟标记
}tree[MAXN << 3];
void pushup(int rt){
tree[rt].val = max( tree[rt << 1].val , tree[rt << 1 | 1].val );
}
void pushdown(int rt)
{
if(tree[rt].lazy)
{
ll p=tree[rt].lazy;
tree[rt<<1].lazy+=p;
tree[rt<<1].val+=p;
tree[rt<<1|1].lazy+=p;
tree[rt<<1|1].val+=p;
tree[rt].lazy=0;
}
}
void build(int l, int r, int rt){
tree[rt].l = l;
tree[rt].r = r;
tree[rt].val = tree[rt].lazy = 0;
if(l == r) return ;
int m = (l + r) >> 1;
build(l, m, rt << 1);
build(m+1, r, rt << 1 | 1);
}
void update(int L, int R, ll state, int rt){
if(L <= tree[rt].l && tree[rt].r <= R){
tree[rt].lazy += state;
tree[rt].val += state;
return ;
}
pushdown(rt);//延迟标记下传
int m = (tree[rt].l + tree[rt].r) >> 1;
if(L <= m)
update(L, R, state, rt << 1);
if(m < R)
update(L, R, state, rt << 1 | 1);
pushup(rt);
}
void solve(){
vector<line> v;
int cnt=0;
for(int i = 1; i <= n; i++){
ll x1, y1,k;
cin >> x1 >> y1 >> k;
v.pb(line{x1, y1, y1+h-1, k});
v.pb(line{x1+w, y1, y1+h-1, -k});
alls[++cnt] = y1;
alls[++cnt] = y1+h-1;
}
sort(alls+1, alls+1+cnt);
lscnt = 0;
alls[0] = -1;
for(int i = 1; i <= cnt; i++)
if(alls[i] != alls[i-1])
ls[++lscnt] = alls[i];
sort(v.begin(), v.end());
build(1, lscnt, 1);//注意,这题是对格点建立线段树
ans = 0;
for(int i = 0; i < 2*n; i++){
update(ask(v[i].y1), ask(v[i].y2), v[i].state, 1);//注意这个是格点问题
ans = max(ans,tree[1].val) ;
}
}
int main()
{
qc;
while(cin >> n >> w>> h){
solve();
printf("%d\n",ans);
memset(alls,0,sizeof(alls));
memset(ls,0,sizeof(ls));
memset(tree,0,sizeof(tree));
}
return 0;
}
在看题解的时候,还发现了一种比较好理解的做法,是枚举+尺取,大家有兴趣也可以去试试。
碎碎念
这题看了我好久,真的有点难度。首先将星星转化为窗户这一点就不太容易想到,然后还有很多的小细节需要思考…