————出自南昌理工学院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;
} ```