线段树+扫描线算法学习笔记

文章介绍了如何使用线段树和扫描线技术求解重叠矩形的总周长和总面积问题,通过构建Segment_Tree结构并实现Cover和Uncover操作来维护覆盖区间信息,展示了在两个具体题目中的应用实例。
摘要由CSDN通过智能技术生成

线段树+扫描线

在求重叠矩形总周长或总面积时,可以用扫描线。

一般要开一个结构体 L I N E LINE LINE,记两端的线段。

struct LINE{
	int x,ya,yb;
	bool In;//加入或删除
	friend bool operator <(LINE a,LINE b){return a.x<b.x;}
}line[N];

模版题

  1. Picture - POJ 1177.

求周长的题目,其实可以横竖分别扫一次,但那样太俗了→_→。

struct LINE{
	int x,ya,yb;
	bool In;
	friend bool operator <(LINE a,LINE b){return a.x<b.x;}
}line[N];
int n,cnt;
int prex,preclen,precnt;
ll ans;
struct Segment_Tree{
	#define ls (p<<1)
	#define rs (p<<1|1)
	int n;
	struct node{
		int l,r,len;//线段端点,长度 
		bool L,R;//左右端点是否覆盖 
		int c,clen,cnt;//被一次性整体覆盖的总层数,总覆盖长度,覆盖区间个数 
		void upd_node(node a,node b){
			L=a.L,R=b.R;
			clen=a.clen+b.clen;
			cnt=a.cnt+b.cnt-(a.R&&b.L);
		}
	}tr[N<<2];
	void init(int _n){
		return (void)(n=_n,build(1,n,1));
	}//初始化 
	void push_up(int p){
		if(tr[p].c){
			tr[p].clen=tr[p].len;
			tr[p].L=tr[p].R=tr[p].cnt=1;
		}else tr[p].upd_node(tr[ls],tr[rs]);
	}//向上传递一层
	void build(int l,int r,int p){
		tr[p]={l,r,r-l+1,0,0,0,0,0};
		if(l==r)return;
		int mid=l+r>>1;
		build(l,mid,ls);
		build(mid+1,r,rs);
		push_up(p);
	}//建树
	#define mid (tr[p].l+tr[p].r>>1)
	void Cover(int p,int l,int r){
		if(l<=tr[p].l&&tr[p].r<=r){
			if(!tr[p].c)tr[p].L=tr[p].R=1,tr[p].cnt=1,tr[p].clen=tr[p].len;
			return ++tr[p].c,void();
		}
		if(mid>=l)Cover(ls,l,r);
		if(mid<r)Cover(rs,l,r);
		push_up(p);
	}
	void Uncover(int p,int l,int r){
		if(l<=tr[p].l&&tr[p].r<=r){
			--tr[p].c;
			if(!tr[p].c){
				if(tr[p].len==1)tr[p].L=tr[p].R=tr[p].cnt=tr[p].clen=0;
				else tr[p].upd_node(tr[ls],tr[rs]);
			}
			return;
		}
		if(mid>=l)Uncover(ls,l,r);
		if(mid<r)Uncover(rs,l,r);
		push_up(p);
	}
    #undef mid
    void upd(LINE a){
    	if(a.In)Cover(1,a.ya,a.yb-1);
    	else Uncover(1,a.ya,a.yb-1);
		ans+=2*precnt*(a.x-prex)+abs(tr[1].clen-preclen);
		precnt=tr[1].cnt;
		prex=a.x;
		preclen=tr[1].clen;
	}
}seg;//维护覆盖区间总长,不连通覆盖区间个数 
signed main(){
	scanf("%d",&n);
	FOR(i,1,n){
		int xa,xb,ya,yb;
		scanf("%d%d%d%d",&xa,&ya,&xb,&yb);
		line[++cnt]={xa,ya+10001,yb+10001,1};
		line[++cnt]={xb,ya+10001,yb+10001,0};
	}
	sort(line+1,line+cnt+1);
	prex=line[1].x;
	seg.init(20001);
	FOR(i,1,cnt)seg.upd(line[i]);
	printf("%lld\n",ans);
	return 0;
}
  1. Atlantis - HDU 1542.

求总重叠面积。

const int N=2e2+10;
struct LINE{
	db x;
	int ya,yb,In;
	friend bool operator <(LINE a,LINE b){return a.x<b.x;}
}line[N];
int cas;
int n,m;
db ans,prex;
db xa[N],ya[N],xb[N],yb[N],b[N];
int nya[N],nyb[N],cnt;
struct Segment_Tree{
	#define ls (p<<1)
	#define rs (p<<1|1)
	int n;
	struct node{
		int l,r,mark,c;
		db len,len2;//区间长度,被一次性整体覆盖次数,覆盖两次总长度
	}tr[N<<2];
	void init(int _n){
		n=_n;build(1,n,1);
	}//初始化 
	void push_down(int p){
		tr[ls].c+=tr[p].mark,tr[ls].mark+=tr[p].mark;
		tr[rs].c+=tr[p].mark,tr[rs].mark+=tr[p].mark;
		tr[p].mark=0;
	}
	void push_up(int p){
		if(tr[p].c)tr[p].len2=tr[p].len;
		else {
			if(tr[p].l==tr[p].r)tr[p].len2=0;
			else tr[p].len2=tr[ls].len2+tr[rs].len2;
		}
	}
	void build(int l,int r,int p){
		tr[p]={l,r,0,0,b[r+1]-b[l],0};
		if(l==r)return;
		int mid=l+r>>1;
		build(l,mid,ls);
		build(mid+1,r,rs);
	}//建树
	#define mid (tr[p].l+tr[p].r>>1)
	void Cover(int p,int l,int r,int d){
		if(l<=tr[p].l&&tr[p].r<=r){
			tr[p].c+=d,tr[p].mark+=d;
			push_up(p);
			return;
		}push_down(p);
		if(mid>=l)Cover(ls,l,r,d);
		if(mid<r)Cover(rs,l,r,d);
		push_up(p);
	}//修改 
    #undef mid
	void query(int p){
		if(tr[p].l==tr[p].r)return push_up(p),void();
		push_down(p);
		query(ls),query(rs);
		return push_up(p),void();
	}
    void upd(LINE a){
    	query(1);
    	ans+=(a.x-prex)*tr[1].len2;
    	Cover(1,a.ya,a.yb-1,a.In);
    	prex=a.x;
	}
}seg;
void C_main(){
	cnt=0,m=0,ans=0;
	FOR(i,1,n){
		cin>>xa[i]>>ya[i]>>xb[i]>>yb[i];
		b[++cnt]=ya[i],b[++cnt]=yb[i];
	}
	sort(b+1,b+cnt+1),cnt=unique(b+1,b+cnt+1)-b-1;
	FOR(i,1,n)nya[i]=lower_bound(b+1,b+cnt+1,ya[i])-b,nyb[i]=lower_bound(b+1,b+cnt+1,yb[i])-b;
	FOR(i,1,n)line[++m]={xa[i],nya[i],nyb[i],1},line[++m]={xb[i],nya[i],nyb[i],-1};
	sort(line+1,line+m+1);
	seg.init(cnt-1);
	prex=line[1].x;
	FOR(i,1,m)seg.upd(line[i]);
	cout<<"Test case #"<<++cas<<endl;
	cout<<"Total explored area: ";
	cout<<fixed<<setprecision(2)<<ans<<endl;
}
signed main(){
	while(cin>>n,n)C_main();
	return 0;
}
  1. 覆盖的面积 - HDU 1255.

上一题的升级版(稍微改改就过了)。

相关资料

  1. 线段树详解 (原理,实现与应用) - AC_King - 博客园 (cnblogs.com).
  2. 线段树详解 (原理,实现与应用)-CSDN博客.
  3. 线段树,扫描线详解(非常详细)-CSDN博客.
  4. 一篇文章搞懂扫描线算法(线段树) HDU1542_线段树扫描线算法-CSDN博客.

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值