【ACWing】248. 窗内的星星——扫描线

原题链接

题目:

在这里插入图片描述
在这里插入图片描述

解题思路:

设某颗星星的坐标为 ( x , y ) (x,y) (x,y) ,那么当窗户的右上角端点的坐标在 ( x ∼ x + w − 1 , y ∼ y + h − 1 ) (x\sim x+w-1 , y\sim y+h-1) (xx+w1,yy+h1) 这个范围内时,星星就会出现在窗户里。(因为题目中说边界的星星不算,所以长和宽都要减1)
在这里插入图片描述
于是我们可以将每个星星都扩展成一个矩形,通过画图我们可以发现:若两个矩形之间有重叠部分,那么他们便可以放在同一个窗户中,那么这个窗户的亮度就是这两颗星星的亮度之和。
在这里插入图片描述
于是,我们可以将问题转化为:平面上有若干个矩形,每个矩形都带有一个权值,求在哪个坐标上权值的总和最大。

接下来我们就可以使用扫描线来解决这个问题了,若当前星星的亮度值为 k , 则矩形的入边的权值设为 k ,出边为 -k ,此时我们只要求扫描线上的区间最大值即可得出答案。

需要注意的几点细节:
1.星星的坐标范围为 0 ≤ x i , y i ≤ 2 31 0 \leq x_i,y_i \leq 2^{31} 0xi,yi231,所以我们需要将坐标进行离散化处理
2.在对扫描线排序时,首先按照 x 坐标进行升序排序;当x坐标重合时,按照 state 值升序排序。这样排序的原因是,当一个矩形的出边和另一个矩形的入边重合的时候,应该先减去出边的星星,因为边界值不算,然后再加上另一个星星的亮度。如果不这样的话,答案可能偏大。
3.创建扫描线的时候,入边是 ( x , y , y + h − 1 , k ) ({x, y, y+h-1, k}) (x,y,y+h1,k) ,出边是 ( x + w , y , y + h − 1 , − k ) ({x+w, y, y+h-1, -k}) (x+w,y,y+h1,k),出边的x坐标不是 x + w − 1 x+w-1 x+w1 ,因为每一条扫描线都应该做加减的操作,也就应该是窗户的边界,所以宽度不需要减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;
}

在看题解的时候,还发现了一种比较好理解的做法,是枚举+尺取,大家有兴趣也可以去试试。

枚举+尺取做法题解

碎碎念

这题看了我好久,真的有点难度。首先将星星转化为窗户这一点就不太容易想到,然后还有很多的小细节需要思考…

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值