扫描线问题
概述
算法核心:扫描。即有一根线从左侧扫描到右侧,在扫描过程中可以统计一些信息。其中对于扫描的每个x,存在垂直x轴的线段,线段信息用线段树维护(注意端点)。
配套使用:离散化(y轴数据范围很大时候,不可能建立很大的线段树)、二分(查找离散化结果)、懒标记(对区间修改)
注意:周长并和面积并的懒标记不需要下传(等同于cnt区间覆盖的次数)
acwing 248. 窗内的星星
思路
- 因为我们已经知道了矩形的长和宽,所以对于任意一个星星(x, y, c), 都可以得到一个确切的矩形,所以每一个星星,我们建立一个固定大小的矩形,矩形的左下角为(x, y), 因为边界上的星星不算,所以右上角表示为(x + w, y + h - 1),目标是求出固定矩形区域内的最大值
- 所以这道题我们可以用扫描线来做,取出每个区域的左右边界,保存俩个四元组(x, y, y + h - 1, c), (x + w, y, y + h - 1, -c)并将这些四元组按照x坐标排序
- 同时用线段树来维护纵坐标的区间最大值, 逐一扫描四元组(x, y1, y2, c), 并执行线段树的修改操作,将[y1, y2]区间上的值都加上c, 不断向上更新父节点最大值,然后得到根节点的dat值得到最终的答案
小结:
build一个空的线段树
将处理好的每个边依次动态加入到线段树中
根据每个边的信息修改线段树。即:入边使区间加上权重,出边减去权重
维护线段树的最大值,每次需要知道全部区间中所有权重的最大值(也就是星星的数量)
代码
#include <bits/stdc++.h>
#define endl "\n"
#define debug(x) cout << #x << ": -----> " << x << endl;
typedef long long ll;
// typedef unsigned long long ull;
#define root tr[u]
#define ls tr[u<<1]
#define rs tr[u<<1|1]
using namespace std;
const int N=1e7+10;
int n,w,h;
struct node{
int l,r;
ll data,add; //data为数量信息(cnt),add为懒标记
} tr[8*N];
struct edge{
int x,y1,y2;
int k; //每个边的权重,入为正,出为负
bool operator<(const edge& m) const{ //根据x排序
if(x==m.x) return k<m.k;
return x<m.x;
}
} seg[N*2];
vector<int> mp; //离散化
void pushup(int u){ //更新维护的区间信息最大值
root.data=max(ls.data,rs.data);
}
void pushdown(int u){ //将标记向下传递
rs.data+=root.add;
ls.data+=root.add;
rs.add+=root.add;
ls.add+=root.add;
root.add=0;
}
void build(int u,int l,int r){
root={l,r,0,0};
if(l==r) return;
else{
int mid=l+r>>1;
build(u<<1,l,mid),build(u<<1|1,mid+1,r);
//空树不用pushup
}
}
void modify(int u,int l,int r,int add){
if(root.l>=l&&root.r<=r){
root.add+=add; //包含在区间中时,执行难修改操作
root.data+=add; //注意:root信息为自己的,懒标记只代表子节点信息
return;
}
else{
if(root.add) pushdown(u); //有标记要先pushdown
int mid=root.l+root.r>>1;
if(l<=mid) modify(u<<1,l,r,add);
if(r>mid) modify(u<<1|1,l,r,add);
pushup(u); //更新
}
}
int main(){
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int t;cin>>t;
while(t--){
cin>>n>>w>>h;
int cnt=0;
mp.clear(); //艹,调了1h的坑
mp.push_back(-0x3f3f3f3f);
for(int i=0;i<n;i++){
int x,y,c; cin>>x>>y>>c;
seg[cnt++]={x,y,y+h-1,c};
seg[cnt++]={x+w,y,y+h-1,-c};
mp.push_back(y); mp.push_back(y+h-1);
}
sort(mp.begin(),mp.end());
mp.erase(unique(mp.begin(),mp.end()),mp.end());
int num=mp.size()-1;
for(int i=0;i<cnt;i++){ //对区间长度没有特别的要求时,这个很方便
seg[i].y1=lower_bound(mp.begin(),mp.end(),seg[i].y1)-mp.begin();
seg[i].y2=lower_bound(mp.begin(),mp.end(),seg[i].y2)-mp.begin();
}
sort(seg,seg+cnt);//按顺序写入边
build(1,1,num);
ll ans=0;
for(int i=0;i<num;i++){
modify(1,seg[i].y1,seg[i].y2,seg[i].k);
ans=max(ans,tr[1].data);
}
cout<<ans<<endl;
}
return 0;
}
acwing 247. 亚特兰蒂斯
大致思路同上
代码
#include <bits/stdc++.h>
#define endl "\n"
#define debug(x) cout << #x << ": -----> " << x << endl;
// typedef long long ll;
// typedef unsigned long long ull;
#define root tr[u]
#define ls tr[u<<1]
#define rs tr[u<<1|1]
using namespace std;
const int N=100010;
int n;
struct Edge{
double x,y1,y2;
int v;
bool operator<(const Edge &t) const{
return x<t.x;
}
}edge[2*N];
struct node{
int l,r;
int cnt;
double len;
} tr[8*N];
vector<double> ys;
int find(double x){//注意数据类型!!!
return lower_bound(ys.begin(),ys.end(),x)-ys.begin();
}
void pushup(int u){
if(root.cnt) root.len=ys[root.r+1]-ys[root.l];
else if(root.l !=root.r){
root.len=rs.len+ls.len;
}
else root.len=0;
}
void build(int u,int l,int r){
if(l==r) root={l,r,0,0};
else{
root={l,r,0,0};
int mid=l+r>>1;
build(u<<1,l,mid),build(u<<1|1,mid+1,r);
// pushup(u);
}
}
void modify(int u,int l,int r,int k){
if(root.l>=l&&root.r<=r){
root.cnt+=k;
pushup(u);
}
else{
int mid=root.r+root.l>>1;
if(l<=mid) modify(u<<1,l,r,k);
if(r>mid) modify(u<<1|1,l,r,k);
pushup(u);
}
}
int main(){
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
// cin>>n;
int T=1;
while(cin>>n,n){
int idx=0;
ys.clear();
for(int i=0;i<n;i++){
double x1,y1,x2,y2;
cin>>x1>>y1>>x2>>y2;
edge[idx++]={x1,y1,y2,1};
edge[idx++]={x2,y1,y2,-1};
ys.push_back(y1),ys.push_back(y2);
}
sort(ys.begin(),ys.end());
ys.erase(unique(ys.begin(),ys.end()),ys.end());
build(1,0,ys.size()-2);
sort(edge,edge+n*2);
double ans=0;
for(int i=0;i<n*2;i++){
if(i>0) ans+=tr[1].len*(edge[i].x-edge[i-1].x);
modify(1,find(edge[i].y1),find(edge[i].y2)-1,edge[i].v);
}
cout<<"Test case #"<<T++<<endl;
cout<<"Total explored area: "<<fixed<<setprecision(2)<<ans<<endl<<endl;
}
return 0;
}
luogu P1856 周长并
这题数据范围并不需要离散化
代码
#include <bits/stdc++.h>
#define endl "\n"
#define debug(x) cout << #x << ": -----> " << x << endl;
typedef long long ll;
// typedef unsigned long long ull;
#define root tr[u]
#define rs tr[u<<1|1]
#define ls tr[u<<1]
using namespace std;
const int N=10005;
int n;
struct edge{
int l,r,len; //len:区间总长度(竖着)(通过作差得到答案)
int add,numx; //add:懒标记(区间被完全覆盖的次数),numx目前在维护几个线段(横着)
bool lt,rt; //是否包含边界,合并用,详细见下
} tr[4*N];
struct node{
int x,y1,y2;
int v;
bool operator<(node &t) const{
if(x==t.x) return v>t.v; //先去边再加边(放置出现在y方向上并列出现的样例)
else return x<t.x;
}
} seg[2*N];
void build(int u,int l,int r){
if(l==r){
tr[u]={l,r,0,0,0,0,0};
return;
}
else{
tr[u]={l,r,0,0,0,0,0};
int mid=l+r>>1;
build(u<<1,l,mid),build(u<<1|1,mid+1,r);
return;
}
}
void pushup(int u){
if(root.add){ //当前区间被覆盖了,他的长度就要统计,不论覆盖了几次,就算一次
root.len=root.r-root.l+1; //维护信息的起源
root.lt=root.rt=1;
root.numx=1;
}
else if(root.l==root.r){ //维护区间信息不需要根节点
root.lt=root.rt=0;
root.len=0;
root.numx=0;
}
else{
root.len=ls.len+rs.len;
root.lt=ls.lt;
root.rt=rs.rt;
root.numx=ls.numx+rs.numx-(ls.rt&rs.lt); //线段树维护的是连续的区间,如果同时覆盖了[1,2],[3,4],这种情况下,他们的(横边)是公用的,需要合并
}
}
void modify(int u,int l,int r,int add){
if(root.l>=l&&root.r<=r){
root.add+=add;
pushup(u);
}
else{
int mid=root.r+root.l>>1; //debug了半天
if(l<=mid) modify(u<<1,l,r,add);
if(r>mid) modify(u<<1|1,l,r,add);
pushup(u);
}
}
int main(){
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin>>n;
int idx=0;
int _max=-0x3f3f3f3f,_min=0x3f3f3f3f;
for(int i=0;i<n;i++){
int x1,y1,x2,y2;cin>>x1>>y1>>x2>>y2;
_min=min(_min,min(y1,y2)); _max=max(_max,max(y1,y2)); //线段树的l,r可以为负
seg[++idx]={x1,y1,y2,1};
seg[++idx]={x2,y1,y2,-1};
}
sort(seg+1,seg+idx+1);
build(1,_min,_max-1); //用线段树维护区间时,长度是点数少一
ll ans=0,last=0;
for(int i=1;i<=idx;i++){
modify(1,seg[i].y1,seg[i].y2-1,seg[i].v); //把每个边加入线段树,维护被覆盖的所有线段长
ans+=abs(tr[1].len-last);
ans+=(seg[i+1].x-seg[i].x)*2*tr[1].numx;
last=tr[1].len;
}
cout<<ans;
return 0;
}
/*
一些特殊样例
3
1 1 6 4
2 3 4 5
5 3 7 5
2
0 0 4 4
0 4 4 8
*/