扫描线学习笔记

第一次写qwq小小的记录一下
自己能看懂就行了

介绍

扫描线扫描线,字面意思就是用一根线扫描一下(雾
Oi Wiki上这个图很直观

我们可以根据扫描线被图形截得的线段来求一些东西,比如图形的面积并啦、周长并啦等等。具体看下面的。

面积并

比如我们想求一些矩形覆盖的面积。参考上面的图,我们可以把整块面积分割成那些彩色小矩形来求。也就是 \(\sum\) 截线段长度*相邻两条截线段的高度差。

相邻两条截线段的高度差很好求,那截线段长度怎么办捏

哦这些黄色的就是截线段,看见他想到了什么。
线段覆盖呀
模拟一下:
① 扫描线最先碰到的矩形的边是最下面的 \([1,4]\) ,我们把 \(cnt[1,4]\) 都加上 \(1\) 。这时只有 \([1,4]\) 是被覆盖的,所以第一根截线段长为 \(3\)
② 接着碰到了第二个矩形的边 \([2,5]\) , 我们把 \(cnt[2,5]\) 都加上 \(1\) 。这次 \([1,5]\) 不为空代表被覆盖了,第二根截线段长为 \(4\)
……
④ 这回碰到了第一个矩形上面的边,它下面的边我们最开始已经加过了,所以现在要删掉,将 \(cnt[1,4]\) 都减掉 \(1\) 。现在不为空的仍为 \(1,6\) ,截线段长为 \(5\)

好像找到了规律。我们把一个矩形从下面的边叫做入边,扫到它的时候将区间每个数 \(+1\) ,把同一个矩形上面的边叫做出边,扫到它的时候将区间每个数 \(-1\) 。被覆盖的长度就是整个区间不为空的长度辣。

诶区间修改区间查询,我们拿线段树来维护一下就好了。
具体操作下面这个讲的很详细了。我不想打了

P5490 【模板】扫描线

点击查看代码
#include <bits/stdc++.h>
#define int long long
#define lson(x) (x<<1)
#define rson(x) (x<<1|1)
using namespace std;
inline int read(){
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
const int N=1e5+5;
struct Seg_tree{
	int l,r,sum,cover;
}tr[8*N];
struct scanline{
	int l,r,h,mark;
	friend bool operator < (scanline x,scanline y){
		return x.h<y.h;
	}
}a[2*N];
int x[2*N],cnt;
void pushup(int i){
	if(tr[i].l==tr[i].r){
		if(tr[i].cover){tr[i].sum=x[tr[i].r+1]-x[tr[i].l];
		}else{tr[i].sum=0;}
	}else{
		if(tr[i].cover){tr[i].sum=x[tr[i].r+1]-x[tr[i].l];
		}else{tr[i].sum=tr[lson(i)].sum+tr[rson(i)].sum;}
	}
}
void build(int l,int r,int i){
	tr[i].l=l,tr[i].r=r;
	if(l==r){return;}
	int mid=(l+r)>>1;
	build(l,mid,lson(i));build(mid+1,r,rson(i));
}
void update(int i,int l,int r,int k){
	if(l<=x[tr[i].l]&&x[tr[i].r+1]<=r){tr[i].cover+=k;pushup(i);return;}
	if(l<x[tr[lson(i)].r+1]){update(lson(i),l,r,k);}
	if(r>x[tr[rson(i)].l]){update(rson(i),l,r,k);}
	pushup(i);
}
signed main(){
	int n;n=read();
	int x1,y1,x2,y2;
	for(int i=1;i<=n;i++){
		x1=read();y1=read();x2=read();y2=read();
		a[++cnt]=(scanline){x1,x2,y1,1};
		x[cnt]=x1;
		a[++cnt]=(scanline){x1,x2,y2,-1};
		x[cnt]=x2;
	}
	sort(x+1,x+cnt+1);
	sort(a+1,a+cnt+1);
	int ans=0;
	build(1,cnt-1,1);
	for(int i=1;i<cnt;i++){
		update(1,a[i].l,a[i].r,a[i].mark);
		ans+=tr[1].sum*(a[i+1].h-a[i].h);
	}
	printf("%lld\n",ans);
	return 0; 
}

周长并

容易想到最简单朴素暴力的方法就是横着扫一遍再竖着扫一遍。
但是我们考虑能不能扫一次就解决横向变和纵向边呢

好哒我们开始从下往上扫,横边的总长就是 \(\sum abs(\) 上次截的线段长 \(-\) 这次截的线段长 \()\) 。理解的话平移法解决。

竖直方向上的长度我们把它看做这些彩色的小棍 显然不包括黄色
然后这些彩色的小棍和什么有关呢 就是看被截的这一行有几个端点,代表有几根小棍,一行的小棍长度是一样的等于相邻两条截线的高度差。
so 竖边总长 \(=\sum\) 端点数 \(*\) 高度。
线段树上维护一个 \(lp,rp\) 表示左右端点的覆盖情况。

P1856 [IOI1998] [USACO5.5] 矩形周长Picture

点击查看代码
#include <bits/stdc++.h>
#define int long long
#define lson(x) (x<<1)
#define rson(x) (x<<1|1)
using namespace std;
const int N=5e3+5;
struct scanline{
	int l,r,h,mark;
	friend bool operator < (scanline x,scanline y){
		return (x.h==y.h)?(x.mark>y.mark):(x.h<y.h);
	}
}a[2*N];
struct Seg_tree{
	int l,r,sum,num,len,lp,rp;
}tr[8*N];
int cnt,x[2*N];
void build(int l,int r,int i){
	tr[i].l=l,tr[i].r=r;
	if(l==r){return;}
	int mid=(l+r)>>1;
	build(l,mid,lson(i));
	build(mid+1,r,rson(i));
}
void pushup(int i){
	if(tr[i].l==tr[i].r){
		if(tr[i].sum){tr[i].lp=tr[i].rp=1;tr[i].num=2;tr[i].len=x[tr[i].r+1]-x[tr[i].l];
		}else{tr[i].lp=tr[i].rp=0;tr[i].num=0;tr[i].len=0;}
	}else{
		if(tr[i].sum){
			tr[i].lp=tr[i].rp=1;tr[i].num=2;tr[i].len=x[tr[i].r+1]-x[tr[i].l];
		}else{
			tr[i].lp=tr[lson(i)].lp;tr[i].rp=tr[rson(i)].rp;
			tr[i].len=tr[lson(i)].len+tr[rson(i)].len;
			tr[i].num=tr[lson(i)].num+tr[rson(i)].num;
			if(tr[lson(i)].rp&&tr[rson(i)].lp)tr[i].num-=2;
		}
	}
}
void update(int i,int l,int r,int k){
	if(l<=x[tr[i].l]&&x[tr[i].r+1]<=r){tr[i].sum+=k;pushup(i);return;}
	if(l<x[tr[lson(i)].r+1])update(lson(i),l,r,k);
	if(r>x[tr[rson(i)].l])update(rson(i),l,r,k);
	pushup(i);
}
signed main(){
	int n;scanf("%lld",&n);
	int x1,y1,x2,y2;
	for(int i=1;i<=n;i++){
		scanf("%lld%lld%lld%lld",&x1,&y1,&x2,&y2);
		a[++cnt]=(scanline){x1,x2,y1,1};x[cnt]=x1;
		a[++cnt]=(scanline){x1,x2,y2,-1};x[cnt]=x2;
	}
	sort(x+1,x+cnt+1);
	sort(a+1,a+cnt+1);
	int len=unique(x+1,x+cnt+1)-x-1;
	build(1,len-1,1);
	int ans=0,last=0;
	for(int i=1;i<=cnt;i++){
		update(1,a[i].l,a[i].r,a[i].mark);
		ans+=abs(tr[1].len-last);last=tr[1].len;
		ans+=tr[1].num*(a[i+1].h-a[i].h);
	}
	printf("%lld\n",ans);
	return 0;
}

于是乎我们有了第一道紫题

posted @ 2024-07-18 21:29  Bao111  阅读(8)  评论(0编辑  收藏  举报