扫描线+线段树简介 AcWing 248窗内的星星题解

									————出自南昌理工学院ACM集训队

这周学习了线段树和扫描线的解题方法,下面由小菜鸡简介一下:
一般扫描线的题目最简单的便是扫描线裸模板(一般来说的话:数据范围小),其次的话便是进行拓展成线段树+扫描线(数据范围增大),再难一点的话就是扫描线+线段树+加上离散化操作了。

扫描线

什么是扫描线呢:
给你一堆的矩形求这些矩形再重叠的情况下的面积?便需要用到扫描线
在这里插入图片描述
按照x排序从小到大 xi(1~8),这样处理之后我们可以看到 每两的相邻的 xi的值把矩形分成了若干个矩形,这是我们记录一下上面的长y2和下面的长y1。总的面积的答案ans+=(x2-x1)*(y2-y1)加上每两个xi之前并且属于题目矩形 ,这里面还涉及一个区间合并(例如x2到x3所形成的矩阵)。

扫描线+线段树
例题:油漆面积
X星球的一批考古机器人正在一片废墟上考古。
该区域的地面坚硬如石、平整如镜。
管理人员为方便,建立了标准的直角坐标系。
每个机器人都各有特长、身怀绝技。
它们感兴趣的内容也不相同。
经过各种测量,每个机器人都会报告一个或多个矩形区域,作为优先考古的区域。
矩形的表示格式为 (x1,y1,x2,y2),代表矩形的两个对角点坐标。
为了醒目,总部要求对所有机器人选中的矩形区域涂黄色油漆。
小明并不需要当油漆工,只是他需要计算一下,一共要耗费多少油漆。
其实这也不难,只要算出所有矩形覆盖的区域一共有多大面积就可以了。
注意,各个矩形间可能重叠。

输入格式
第一行,一个整数 n,表示有多少个矩形。
接下来的 n 行,每行有 4 个整数 x1,y1,x2,y2,空格分开,表示矩形的两个对角顶点坐标。
输出格式
一行一个整数,表示矩形覆盖的总面积。
数据范围
1≤n≤10000,
0≤x1,x2,y2,y2≤10000
数据保证 x1<x2 且 y1<y2。
输入样例:
3
5 2 10 6
2 7 12 10
8 1 15 15
输出
128

需要结构体保存四元组(x,y1,y2,k)x代表这条边算在x轴的位子,y1、y2代表这条边在x的下限和上限。k表示标记。

 #include<bits/stdc++.h>
 using namespace std;
 typedef long long ll;
 const int N=10100;//数据范围
 int ls(int x){//左节点
 	return x<<1;
 } 
 int rs(int x){//右节点
 	return x<<1|1;
 }
 struct node{
 	int x,y1,y2;//x代表这条边算在x轴的位子,y1、y2代表这条边在x的下限和上限
 	int k;//k表示标记。
 }seg[N*2];//一个矩阵有两条平行于y轴的边,所以要乘2
 struct Node{
 	int l,r;
 	int cnt,len;
 }; 
 Node tree[N*4];//建树的存储大小
 int n;
 void build(int l,int r,int u){//建线段树
 	tree[u]={l,r};
	 if(r==l)
 		return ;
 	int mid=(l+r)>>1;
 	build(l,mid,ls(u));
 	build(mid+1,r,rs(u));
 }
 void push_up(int p){//向上更新
 	if(tree[p].cnt>0){
 		tree[p].len=tree[p].r-tree[p].l+1;
	 }
	 else if(tree[p].l==tree[p].r) tree[p].len=0;
	 else tree[p].len=(tree[ls(p)].len+tree[rs(p)].len);
 }
bool cmp(node a,node b){
	return a.x<b.x;
} 
void modify(int u,int l,int r,int k){//更新每一条平行于Y轴的边
	if(tree[u].l>=l&&tree[u].r<=r){
		tree[u].cnt+=k;
		push_up(u);
	}else{
	int mid=(tree[u].r+tree[u].l)>>1;
	if(mid>=l){
		modify(ls(u),l,r,k);
	}
	if(mid<r){
		modify(rs(u),l,r,k);
	}
	push_up(u);
	}
}
 int main(){
 	cin>>n;
 	int m=0;
 	for(int i=1;i<=n;i++){
 		int x1,x2,y1,y2;
		cin>>x1>>y1>>x2>>y2;
 		seg[m++]={x1,y1,y2,1};//每一次先存正标记
 		seg[m++]={x2,y1,y2,-1};
	 }
	 sort(seg,seg+m,cmp); //按x大小从小到大排完序之后还是正标记在前(指的是同一矩阵的两条边)
	 build(0,10000,1);
	 int ans=0;
	 for(int i = 0; i < m; i ++){
	 	if(i>0) ans+=tree[1].len*(seg[i].x-seg[i-1].x);//每一次加的是相邻的两个xi之间并且在矩形中的部分。
	 	modify(1,seg[i].y1,seg[i].y2-1,seg[i].k);
	 }
	 cout<<ans<<endl;
	 return 0;
 } 

离散化:

vector<int> mp;
for(int i=0;i<=n;i++)
mp.push_back(a[i]);
sort(mp.begin().mp.end());
mp.erase(unique(mp.begin(),mp.end()),mp.end());

线段树+扫描线+离散化

离散化:坐标大就离散化
二分:离散化就二分
扫描线:数据范围大,又是二维,还是要扫描线
线段树:数据范围大,区间可加性,一堆区间操作,还是要线段树

例题:AcWing248窗内的星星
在一个天空中有很多星星(看作平面直角坐标系),已知每颗星星的坐标和亮度(都是整数)。
求用宽为 W、高为 H 的矩形窗口(W,H 为正整数)能圈住的星星的亮度总和最大是多少。(矩形边界上的星星不算)

输入格式
输入包含多组测试用例。
每个用例的第一行包含 3 个整数:n,W,H,表示星星的数量,矩形窗口的宽和高。然后是 n 行,每行有 3 个整数:x,y,c,表示每个星星的位置 (x,y) 和亮度。没有两颗星星在同一点上。

输出格式
每个测试用例输出一个亮度总和最大值。

每个结果占一行。

数据范围
1≤n≤10000,
1≤W,H≤1000000,
0≤x,y<231
输入样例:
3 5 4
1 2 3
2 3 2
6 3 1
3 5 4
1 2 3
2 3 2
5 3 1
将问题转化为:平面上由若干个区域,每个区域都带有一个权值,求在哪个坐标上重叠的区域权值和最大.记住,每一个区域都是有一个星星产生的,权值等于星星的亮度.这里要注意在这个长为W 高为H的矩阵的边界上的星星不算所以等会建树的时候要注意。

 #include<bits/stdc++.h>
 using namespace std;
 typedef long long ll;
 const int N=10100;
 vector<ll> mp;
 int ls(int x){
 	return x<<1;
 } 
 int rs(int x){
 	return x<<1|1;
 }
 struct node{
 	ll x,y1,y2;
 	ll k;
 }seg[N*2];
 struct Node{
 	ll l,r;
 	ll cnt,len;
 }; 
 Node tree[N*8];
 ll n;
 void build(ll l,ll r,ll u){
 	tree[u]={mp[l],mp[r],0,0};
	 if(r - l == 1)//考虑到相邻的不能要。
 		return ;
 	ll mid=(l+r)>>1;
 	build(l,mid,ls(u));
 	build(mid,r,rs(u));
 }
void  push_up(ll p){
 	tree[p].len=max(tree[rs(p)].len,tree[ls(p)].len)+tree[p].cnt;
 }
bool cmp(node a,node b){
	return a.x<b.x|| (a.x==b.x && a.k<0);
} 
void modify(ll u,ll l,ll r,ll k){
	if(tree[u].l>=l&&tree[u].r<=r){
		tree[u].cnt+=k;
		tree[u].len+=k; 
		return ;
		}
		ll mid=l+r>>1;
	if(l<tree[ls(u)].r){
		modify(ls(u),l,min(r,tree[ls(u)].r),k);
	}
	if(r>tree[rs(u)].l){
		modify(rs(u),max(l,tree[rs(u)].l),r,k);
	}
	push_up(u);
}
 int main(){
 	ll n,h,w;
 	while(cin>>n>>w>>h){
 		ll m=0;
 	for(int i=1;i<=n;i++){
 		ll x,y,k;
		cin>>x>>y>>k;
 		seg[m++]={x,y,y+h,k};
 		seg[m++]={x+w,y,y+h,-k};
 		mp.push_back(y);
 		mp.push_back(y+h);
	 }
	 sort(seg,seg+m,cmp); 
	 sort(mp.begin(),mp.end());
	 mp.erase(unique(mp.begin(),mp.end()),mp.end());
	 build(0,mp.size()-1,1);
	 ll ans=0;
	 for(int i = 0; i < m; i ++){
	 	int tmp=lower_bound(mp.begin(),mp.end(),seg[i].y1)-mp.begin();
	 	int tmp1=lower_bound(mp.begin(),mp.end(),seg[i].y2)-mp.begin();
	 	modify(1,mp[tmp],mp[tmp1],seg[i].k);
	 	if(seg[i].k>0)ans=max(ans,tree[1].len);
	 }
	 cout<<ans<<endl;
	 }
 	
	 return 0;
 } ```

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值