扫描线(板子整理) 矩形面积并与矩形周长并

前置知识:离散化,线段树基础即可,难度不大,重在思维

矩形面积并 

扫描线,矩形面积并(洛谷)icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P5490

为了归并区间中的关系(每一段都是连起来的,所以要右端点偏移映射,后面在代码中体现)

与常规维护懒标记不同,这里是向上维护的,最后返回一个tree.len[1],就是根节点的值,就是答案

扫描线(板子整理) 矩形面积并与矩形周长并_第1张图片

(图片来源于董晓老师的博客)

即每一段区间右边那个位置(在离散化后的数组中)右偏了,直接体现在操作中而不是结构中

#include
#include
using namespace std;
const int N=2e5;     //给的数据是1e5,但是要开到2e5,因为离散化要两倍的空间(后面n乘2了)
using ll=long long;
#define lc p<<1
#define rc p<<1|1
struct line{        //维护扫描线
	int x1,x2,y,tag;
}l[N];
struct Tree{        //维护每一段的长度,这里是把长度离散化,宽度直接用上方存的之差来计算,求面积
	int l,r,len,cnt;
}tr[8*N];           
//线段树开4倍,开8倍是因为这里的pushup会向下查,所以最后一层的后面还需要一层
bool cmp(line a,line b){
	return a.y>1;
	build(lc,l,m);
	build(rc,m+1,r);
}
void pushup(int p){//往上更新
	int l=tr[p].l;int r=tr[p].r;//求区间
	if(tr[p].cnt) tr[p].len=d[r+1]-d[l];//如果已经覆盖过,直接计算全段
	else tr[p].len=tr[lc].len+tr[rc].len;//如果没覆盖这一段,就要看子树中覆盖了多少
}
void update(int p,int l,int r,int k){
	if(l<=tr[p].l && tr[p].r<=r){
		tr[p].cnt+=k;pushup(p);return;//这里也要pushup
	}
	int m=(tr[p].l+tr[p].r)>>1;//小心隐式l与r  要区分
	if(l<=m) update(lc,l,r,k);
	if(r>m) update(rc,l,r,k);
	pushup(p);
}
int main(){
	cin>>n;
	for(int i=1;i<=n;++i){
		int a,b,c,e;cin>>a>>b>>c>>e;
		l[i]={a,c,b,1};
		l[i+n]={a,c,e,-1};//-1是出边,1是出边,cnt是状态,如果是0就是没有覆盖
		d[i]=a;d[i+n]=c;//左右都要记录,后面排序
	}
	n*=2;
	sort(l+1,l+1+n,cmp);
	sort(d+1,d+1+n);
	int lens=unique(d+1,d+1+n)-(d+1);//离散化,注意这里减d+1,返回的是排序后的下一个位置
	build(1,1,lens-1);//只需要lens-1个区间位置即可
	ll ans=0;
	for(int i=1;i

矩形周长并

矩形周长并(洛谷)icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P1856

这里如果想用便捷的做法,可以考虑维护两棵线段树,竖着计算一次,横着计算一次就好了

否则,可以引进几个新节点,其他皆在上一部分的基础上进行

以下是计算竖边和横边的思想,感觉是精髓

扫描线(板子整理) 矩形面积并与矩形周长并_第2张图片(这里要注意,两边相减是相离的情况,所以排序要加一个入边优先的判断)


#include
#include
using namespace std;
const int N=1e4+20;   //这里要开到N的两倍,离散化是两倍空间
#define lc p<<1       //
#define rc p<<1|1
using ll=long long;//本题
struct line{
	int x1,x2,y,tag;
}L[N];
bool cmp(line a,line b){
	return a.y==b.y ? a.tag>b.tag : a.y < b.y;
}
struct Tree{
	int l,r,cnt,len;//cnt是被覆盖,是可以多次被覆盖的
	int sum;//sum记录竖线的个数
    bool lcover,rcover;//记录左右端点是否覆盖
    //因此在合并的时候就可以判断要不要在合并的时候把中间那两条干掉
}tr[8*N];
int w[N];
int n;
void build(int p,int l,int r){
	tr[p]={l,r};
	if(l==r) return;
	int m=(l+r)>>1;
	build(lc,l,m);//l-r  注意不要写成1
	build(rc,m+1,r);
}
void pushup(int p){
	int l=tr[p].l;int r=tr[p].r;
	if(tr[p].cnt){
		tr[p].len=w[r+1]-w[l];
		tr[p].lcover=tr[p].rcover=1;//因为有,所以左右都覆盖
		tr[p].sum=2;//就是左右两条竖线都取
	}
	else{
		tr[p].len=tr[lc].len+tr[rc].len;//因为不是全覆盖,就要看子树覆盖了多少
		tr[p].sum=tr[lc].sum+tr[rc].sum;
		tr[p].lcover=tr[lc].lcover;//更新左右端点有没有覆盖
		tr[p].rcover=tr[rc].rcover;
		if(tr[lc].rcover&&tr[rc].lcover) tr[p].sum-=2;//如果中间那里合起来了,就减去中间两条
	}
}
void update(int p,int l,int r,int k){
	if(tr[p].l>=l&&tr[p].r<=r){
		tr[p].cnt+=k;pushup(p);return;   //如果在范围内就直接更新覆盖关系
	}
	int m=(tr[p].l+tr[p].r)>>1;//这里的m是更新区间的中点
	if(l<=m) update(lc,l,r,k);
	if(r>m) update(rc,l,r,k);
	pushup(p);
}


int main(){
	cin>>n;
	for(int i=1;i<=n;++i){
		int a,b,c,d;cin>>a>>b>>c>>d;  //x1 y1 x2 y2
		L[i]={a,c,b,1};L[i+n]={a,c,d,-1};
		w[i]=a;w[i+n]=c;
		
	}
	n*=2;
	sort(L+1,L+1+n,cmp);
	sort(w+1,w+1+n);
    int lens=unique(w+1,w+1+n)-(w+1);
    build(1,1,lens-1);//记着要-1,当然如果不减掉也没有错
	ll ans=0;
	int last=0;
	for(int i=1;i

你可能感兴趣的:(算法,c++,数据结构)