关于计算几何中的扫描线
推荐这篇大佬的博客和这篇
此篇博客仅作为个人的整理
扫描线一般适用于求几个矩形(有重叠)面积和或者周长和。
我们的办法类似这张图
PS:
- 空间要开的足够大,我们这里的线段树跟普通线段是有差异,空间最好开8倍 。
- 变量名y1如果用,会有问题,
最好办法:不用
求面积和模板(带离散化的版本):
//本模板是从坐往右扫的,从下往上扫同理
#define ls (rt<<1)
#define rs (rt<<1|1)
LL cover[N*8];//存放i节点对应区间覆盖情况的值
LL n;
LL len[N*8];
LL yy[N*2];//存放离散后的y值,下标用lowerbound进行查找
LL cnt = 0;
struct node
{
LL x;
LL up,down;;//边的y坐标上,y坐标下
LL inout;//入边为1,出边为-1
}line[N*2],u;
bool cmp(node a,node b)
{
if( a.x==b.x ) return a.inout>b.inout;//一定是加边在前面!!!
return a.x < b.x;
}
void pushup( LL l,LL r,LL rt )//pushup其实主要就思考在什么情况,需要更新哪些信息来维护线段树
{
if( cover[rt]>0 ) len[rt] = yy[r] - yy[l];//如果某个节点的cover为正,那么这个点对应区间的长度全为有效的
else if( l+1==r ) len[rt] = 0;//到了叶子节点
else len[rt] = len[ls] + len[rs];
}
void update( LL yl,LL yr,LL pd,LL l,LL r,LL rt )
{
if( yl>r || yr<l ) return ;
if( yl<=l && yr>=r )
{
cover[rt] += pd;//根据出边入边,加上相应的值
pushup( l,r,rt );
return ;
}
if( l+1==r ) return ;//到子节点,这句一定要有!!!
LL mid = (l+r)>>1;
if( yl<=mid )
{
update( yl,yr,pd,l,mid,ls );
}
if( yr>mid )
{
update( yl,yr,pd,mid,r,rs );//这里不再是m+1,因为要进入类似[1,2][2,3]的叶子节点
}
pushup( l,r,rt );
}
void solve()
{
cnt = 0;
scanf("%lld",&n);
for(LL i=1;i<=n;i++)
{
LL x1,x2,y11,y2;
scanf("%lld %lld %lld %lld",&x1,&y11,&x2,&y2);
u.x = x1;u.down = y11,u.up = y2,u.inout = 1;
line[ ++cnt ] = u;//给入边赋值
yy[cnt] = y11;//获得y值
u.x = x2;u.down = y11,u.up = y2,u.inout = -1;
line[ ++cnt ] = u;
yy[cnt] = y2;
}
sort( yy+1,yy+cnt+1 );//给yy排个序
sort( line+1,line+cnt+1,cmp );//给line按照x轴方向从左到右排序
LL length = unique( yy+1,yy+cnt+1 ) - (yy+1);//进行离散化操作,unique返回重复位置指针,减去(头指针+1)是数组开始的地方得到数组长度
memset( cover,0,sizeof( cover ) );
memset( len,0,sizeof(len) );
LL ans = 0;
for(LL i=1;i<=cnt;i++)
{
ans += len[1] * ( line[i].x - line[i-1].x );
LL yl = lower_bound( yy+1,yy+length+1,line[i].down ) - yy;
LL yr = lower_bound( yy+1,yy+length+1,line[i].up ) - yy;
LL pd = line[i].inout;
update( yl,yr,pd,1,length,1 );
}
printf("%lld\n",ans);
}
一道模板题
题目描述
求 n 个矩形的面积并。
输入格式
第一行一个正整数 n。
接下来 nn 行每行四个非负整数 x 1 , y 1 , x 2 , y 2 x_1, y_1, x_2, y_2 x1,y1,x2,y2,表示一个矩形的左下角坐标为 ( x 1 , y 1 ) (x_1, y_1) (x1,y1) ,右上角坐标为 ( x 2 , y 2 ) (x_2, y_2) (x2,y2)
输出格式
一行一个正整数,表示
n
n
n 个矩形的并集覆盖的总面积。
ACcode:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
//#include<cmath>
#define LL long long
#define N 100005
#define ls (rt<<1)
#define rs (rt<<1|1)
using namespace std;
LL cover[N*10],n;
LL len[N*10],yy[N*2],cnt = 0;
struct node
{
LL x,upy,downy,inout;
}line[N*2],u;
bool cmp(node a,node b)
{
return a.x < b.x;
}
void pushup( LL l,LL r,LL rt )
{
if( cover[rt]>0 ) len[rt] = yy[r] - yy[l];
else if( l+1==r ) len[rt] = 0;
else len[rt] = len[ls] + len[rs];
}
void update( LL yl,LL yr,LL pd,LL l,LL r,LL rt )
{
if( yl>r || yr<l ) return ;
if( yl<=l && yr>=r )
{
cover[rt] += pd;
pushup( l,r,rt );
return ;
}
if( l+1==r ) return ;
LL mid = (l+r)>>1;
if( yl<=mid )
{
update( yl,yr,pd,l,mid,ls );
}
if( yr>mid )
{
update( yl,yr,pd,mid,r,rs );
}
pushup( l,r,rt );
}
void solve()
{
cnt = 0;
scanf("%lld",&n);
for(LL i=1;i<=n;i++)
{
LL x1,x2,y11,y2;
scanf("%lld %lld %lld %lld",&x1,&y11,&x2,&y2);
u.x = x1;u.downy = y11,u.upy = y2,u.inout = 1;
line[ ++cnt ] = u;
yy[cnt] = y11;
u.x = x2;u.downy = y11,u.upy = y2,u.inout = -1;
line[ ++cnt ] = u;
yy[cnt] = y2;
}
sort( yy+1,yy+cnt+1 );
sort( line+1,line+cnt+1,cmp );
LL length = unique( yy+1,yy+cnt+1 ) - (yy+1);
memset( cover,0,sizeof( cover ) );
memset( len,0,sizeof(len) );
LL ans = 0;
for(LL i=1;i<=cnt;i++)
{
ans += len[1] * ( line[i].x - line[i-1].x );
LL yl = lower_bound( yy+1,yy+length+1,line[i].downy ) - yy;
LL yr = lower_bound( yy+1,yy+length+1,line[i].upy ) - yy;
LL pd = line[i].inout;
update( yl,yr,pd,1,length,1 );
}
printf("%lld\n",ans);
}
signed main()
{
//freopen("in.txt","r",stdin);
solve();
return 0;
}
一些相关题目:
T1 窗口的星星
参考
idea:题意不再过多赘述,首先我们思考一个问题,就是在什么条件下星星才会出现在窗户中。
设某颗星星的坐标为
(
x
,
y
)
(x,y)
(x,y),则当窗户的右上角端点的坐标出现在
(
x
∼
x
+
w
−
1
,
y
∼
y
+
h
−
1
)
(x∼x+w−1,y∼y+h−1)
(x∼x+w−1,y∼y+h−1) 这个范围内时,星星就会出现在窗户里。比如下图
因为题目中说出现在窗户边框的星星不算,所以边界坐标要 −1 ,即 ( x + w − 1 , y + h − 1 ) (x+w-1,y+h-1) (x+w−1,y+h−1)。
于是我们可以将每个星星都扩展成一个矩形,这时我们注意到,若两个矩形之间有交集,他们便可以放在同一个窗户中。
图中灰色的部分就是两个星星构成的矩形的交集,只要窗户的右上角端点在灰色区域内,就能同时框住两个星星。
此时我们可以将问题转化为:平面上有若干个矩形,每个矩形都带有一个权值,求在哪个坐标上权值的总和最大。
接下来我们就可以使用扫描线来解决这个问题了,若当前星星的亮度值为 l l l ,则矩形的入边的权值设为 l l l,出边为 − l -l −l ,此时我们只要求扫描线上的区间最大值即可得出答案,区间查询可以使用 lazy_tag 的方式实现。
注意:这里用回了普通线段树,因为我们需要的是一个一个点,不是一个个区间了
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
#define INF 0x3f3f3f3f
#define N 101010
#define ls (p<<1)
#define rs (p<<1|1)
#define int long long
using namespace std;
int w,h,n,m,tot = 0;
LL yy[N*2],cover[N*10],ans,add[N*10];
struct node
{
LL x,downy,upy,val;
bool operator < (const node a)const
{
if( x!=a.x ) return x<a.x;
else return val > a.val;
}
}e[N*2];
void pushdown(int p)
{
cover[ls] += add[p];
cover[rs] += add[p];
add[ls] += add[p];
add[rs] += add[p];
add[p] = 0;
}
void pushup(int p)
{
cover[p] = max( cover[ls] , cover[rs] );
}
void update(int yl,int yr,int pd,int l,int r,int p)
{
if( yl>r || yr<l ) return ;
if( yl<=l&&r<=yr )
{
cover[p] += pd;
add[p] += pd;
return ;
}
if( l==r ) return ;
pushdown( p );
int mid = (l+r)>>1;
if( yl<=mid ) update( yl,yr,pd,l,mid,ls );
if( yr>mid ) update( yl,yr,pd,mid+1,r,rs );//这里是从mid+1开始的
pushup(p);
}
void solve()
{
tot = 0;
int x1,x2,y1,y2,val;
scanf("%lld %lld %lld",&n,&w,&h);
for(int i=1;i<=n;i++)
{
scanf("%lld %lld %lld",&x1,&y1,&val);
x2 = x1 + w - 1;
y2 = y1 + h - 1;
e[ ++tot ] = node{ x1,y1,y2,val };
yy[tot] = y1;
e[ ++tot ] = node{ x2,y1,y2,-1*val };
yy[tot] = y2;
}
sort( e+1,e+tot+1 );
sort( yy+1,yy+tot+1 );
int length = unique( yy+1,yy+tot+1 ) - ( yy+1 );
for(int i=1;i<=tot;i++)
{
int yl = lower_bound( yy+1,yy+length+1,e[i].downy ) - yy;
int yr = lower_bound( yy+1,yy+length+1,e[i].upy ) - yy;
update( yl,yr,e[i].val,1,length,1 );
ans = max( ans , cover[1] );
}
printf("%lld\n",ans);
memset(cover,0,sizeof( cover ));
memset(add,0,sizeof( add ));
ans = 0;
}
signed main()
{
// freopen("in.txt","r",stdin);
int t;
scanf("%d",&t);
while( t-- ) solve();
return 0;
}
T2 CF377D Developing Game
有
n
n
n个人,每人有属性
l
i
,
v
i
,
r
i
(
l
i
≤
v
i
≤
r
i
)
l_i,v_i,r_i(l_i\leq v_i\leq r_i)
li,vi,ri(li≤vi≤ri)
要求选出最大的人的集合 S 使得
∀
x
,
y
∈
S
\forall x,y\in S
∀x,y∈S 有
l
y
≤
v
x
≤
r
y
l_y\leq v_x\leq r_y
ly≤vx≤ry
简单分析一下这些不等式可以发现题目的要求可以转化为: max i ∈ S l i ≤ min i ∈ S v i ≤ max i ∈ S v i ≤ min i ∈ S r i \max\limits_{i\in S} l_i\leq \min\limits_{i\in S} v_i\leq \max\limits_{i\in S} v_i\leq\min\limits_{i\in S} r_i i∈Smaxli≤i∈Sminvi≤i∈Smaxvi≤i∈Sminri
进一步地,我们将要求转化为找到 L , R L,R L,R,使得
max i ∈ S l i ≤ L ≤ min i ∈ S v i \max\limits_{i\in S} l_i\leq L\leq \min\limits_{i\in S} v_i i∈Smaxli≤L≤i∈Sminvi
max
i
∈
S
v
i
≤
R
≤
min
i
∈
S
r
i
\max\limits_{i\in S} v_i\leq R\leq\min\limits_{i\in S} r_i
i∈Smaxvi≤R≤i∈Sminri
同时成立
那么我们将 ( L , R ) (L,R) (L,R)看作二维平面上的一个点,一个人就对应着一个矩形。集合中选出多个人,对 ( L , R ) (L,R) (L,R)的限制就是这些矩形的交。选出最多的人,就是要找到一个点被尽可能多的矩形覆盖。
找到这个点可以用扫描线+线段树。记录最优的点的位置,输出方案时挨个判断矩形是否包含该点。
关于记录方案在主时钟有所提及
ACcode:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define LL long long
#define N 101010
#define ls (p<<1)
#define rs (p<<1|1)
using namespace std;
struct node
{
int x,downy,upy,val;
bool operator < (const node a)const
{
if( x!=a.x ) return x<a.x;
else return val > a.val;
}
}line[2*N];
int yy[N*2],n,tot = 0,pos[N*8],cover[N*8],tag[N*8];//pos数组用于记录答案,就是当前这个区间cover最大值在哪
struct node2
{
int l,v,r;
}a[N];
void pushdown(int p)
{
cover[ls] += tag[p];
cover[rs] += tag[p];
tag[ls] += tag[p];
tag[rs] += tag[p];
tag[p] = 0;
}
void pushup(int p)
{
if( cover[ls]>cover[rs] )//在pushup过程中记录一下这个点区间中cover值最大在哪
{
cover[p] = cover[ls];
pos[p] = pos[ls];
}
else
{
cover[p] = cover[rs];
pos[p] = pos[rs];
}
}
void update(int yl,int yr,int pd,int l,int r,int p)
{
// if( yr<l || yl>r ) return ;
if( yl<=l&&r<=yr )
{
cover[p] += pd;
tag[p] += pd;
return ;
}
if( l==r ) return ;
pushdown(p);
int mid = (l+r)>>1;
if( yl<=mid )
{
update( yl,yr,pd,l,mid,ls );
}
if( yr>mid )
{
update( yl,yr,pd,mid+1,r,rs );//注意从mid+1开始
}
pushup(p);
}
void build(int p,int l,int r)
{
if( l==r )
{
pos[p] = l;
return ;
}
int mid = (l+r)>>1;
build( ls,l,mid );
build( rs,mid+1,r );
pos[p] = pos[p<<1];//用build函数主要是为了初始化pos数组
}
void solve()
{
memset(cover,0,sizeof(cover));
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int l,r,v;
scanf("%d %d %d",&l,&v,&r);
line[ ++tot ] = node{ l,v,r,1 };
yy[tot] = v;
line[ ++tot ] = node{ v,v,r,-1 };
yy[tot] = r;
a[i] = node2{ l,v,r };
}
sort(line+1,line+tot+1);
sort(yy+1,yy+tot+1);
int length = unique( yy+1,yy+tot+1 ) - (yy+1);
build( 1,1,length );
int ans = 0,L,R;
for(int i=1;i<=tot;i++)
{
int yl = lower_bound( yy+1,yy+length+1,line[i].downy ) - yy;
int yr = lower_bound( yy+1,yy+length+1,line[i].upy ) - yy;
update( yl,yr,line[i].val,1,length,1 );
if( ans < cover[1] )
{
ans = cover[1];
L = line[i].x;
R = pos[1];
// cout<<L<<" "<<yy[R]<<endl;
}
}
printf("%d\n",ans);
for(int i=1;i<=n;i++)
{
if( a[i].l<=L&&L<=a[i].v&&a[i].v<=yy[R]&&yy[R]<=a[i].r )
{
printf("%d ",i);
}
}
printf("\n");
}
int main()
{
// freopen("in.txt","r",stdin);
solve();
return 0;
}
T3 (周长并模板题)[USACO5.5]矩形周长Picture
idea:扫两边即可,相邻两次修改的区间覆盖长度差(len的差)加起来就是答案。
ACCode:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define LL long long
#define N 101010
#define ls (rt<<1)
#define rs (rt<<1|1)
using namespace std;
struct node
{
int x,down,up,val;
bool operator < (const node a)const
{
if( x==a.x ) return val > a.val;
else return x < a.x;
}
}line1[N*2],line2[N*2];
int n,x1,x2,y1,y2,tot1 = 0,tot2 = 0,yy[N*2],xx[N*2],ans = 0,len[N*8];
int cover[N*8];
void pushup(int l,int r,int rt,int seg[])
{
if( cover[rt]>0 ) len[rt] = seg[r] - seg[l];
else if( l+1==r ) len[rt] = 0;
else len[rt] = len[ls] + len[rs];
return ;
}
void update(int yl,int yr,int pd,int l,int r,int rt,int seg[])
{
if( yl>r || yr<l ) return ;
if( yl<=l && r<=yr )
{
cover[rt] += pd;
pushup(l,r,rt,seg);
return ;
}
if( l+1==r ) return ;
int mid = (l+r)>>1;
if( yl<=mid ) update( yl,yr,pd,l,mid,ls,seg );
if( yr>mid ) update( yl,yr,pd,mid,r,rs,seg );
pushup(l,r,rt,seg);
}
void solve()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d %d %d %d",&x1,&y1,&x2,&y2);
line1[ ++tot1 ] = node{ x1,y1,y2,1 };
yy[ tot1 ] = y1;
line1[ ++tot1 ] = node{ x2,y1,y2,-1 };
yy[ tot1 ] = y2;
line2[ ++tot2 ] = node{ y1,x1,x2,1 };
xx[ tot2 ] = x1;
line2[ ++tot2 ] = node{ y2,x1,x2,-1 };
xx[ tot2 ] = x2;
}
sort(line1+1,line1+1+tot1);
sort(line2+1,line2+1+tot2);
sort( yy+1,yy+tot1+1 );
sort( xx+1,xx+tot2+1 );
int leny = unique( yy+1,yy+tot1+1 ) - (yy+1);
int lenx = unique( xx+1,xx+tot2+1 ) - (xx+1);
int last;
last = 0;
for(int i=1;i<=tot1;i++)
{
int yl = lower_bound( yy+1,yy+leny+1,line1[i].down ) - yy;
int yr = lower_bound( yy+1,yy+leny+1,line1[i].up ) - yy;
update( yl,yr,line1[i].val,1,leny,1,yy );
ans += abs( len[1] - last );
last = len[1];
}
last = 0;
memset(cover,0,sizeof(cover));
memset(len,0,sizeof(len));
for(int i=1;i<=tot2;i++)
{
int xl = lower_bound( xx+1,xx+lenx+1,line2[i].down ) - xx;
int xr = lower_bound( xx+1,xx+lenx+1,line2[i].up ) - xx;
update( xl,xr,line2[i].val,1,lenx,1,xx );
ans += abs( len[1] - last );
last = len[1];
}
cout<<ans;
}
int main()
{
//freopen("in.txt","r",stdin);
solve();
return 0;
}