扫描线扫走你的不开心

本文探讨了一道涉及二维平面上的面积计算问题,通过引入线段树数据结构进行优化。作者指出,由于问题涉及到两个维度,可以将一维信息用线段树维护,简化二维问题。线段树的节点维护区间内被覆盖的线段长度,通过懒惰标记优化避免递归。在编码实现中,进行了离散化处理以减少复杂度,并在处理区间边界时进行了特殊判断。此外,文章还提到了另一道题目‘窗口的星星’,强调了解题时要根据实际情况调整策略,而非盲目套用模板。
摘要由CSDN通过智能技术生成

前言

一个脱了好久的算法,其实不是很难,只不过之前属实是懒死了,就没去学,上一次模拟赛吃亏了,现在来补一下

解决啥

其实就是二位平面上的面积问题等等,其实好像二位数点也可以做2

正文

首先我们考虑一下如何去暴力的做这道题,是不是大概就是递归之类的

其实我们可以发现,这个题目难在我们有两个维度,那么我们就考虑把一维放入线段树里面去,因为线段树维护的就是区间信息啊。

考虑一下线段树维护啥,其实就是l,r这一段区间里面被矩形覆盖的线段的长度,扫到一个线,去看一看他是入边还是出边,入就这一段全是1,出就去除。之后我们这第二维度是不是就化简了

那么我们去考虑一下如何维护这个线段树上的信息

1。左右儿子如何合并:首先发现假如说这个区间被完全覆盖了,我们拿儿子来维护就会多算or少算,所以我们直接用 r - l赋值就好了,假如说没有的话是不是就拿左右儿子去维护。

2。是否需要懒标记:我们仔细想一下,发现好像并不需要让儿子区间知道自己是否被覆盖,因为我们的线段树维护的是现在扫到的线段长度,相反假如说两条线段,一条覆盖了父节点的全部,一条覆盖了子节点的全部,之后我们取删除覆盖父节点的那个线段,我们会发现可以自动的吧儿子的信息传递到父亲上面,就不用再去麻烦的递归了,

3。懒标记维护啥:标记都没有维护个毛线

离散化

最后就是离散化,这道题的范围太大了,就稍微离散化一下,之后我们发现会有一个问题,对于一个线段树节点维护3,3这个区间,我们发现值就变成了Y[3] - Y[3] ,但是我们要的是Y[3] - Y[2] 怎么办,其实就是把l,r的定义改变一下,一个区间l,r维护的是Y[r+1]-Y[l]这样就好了。

coding time

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 10;
int  n , x , y , x_ , y_ , Y[maxn * 2];
struct line{
	int l , r , x;
	int mark;
}sea[maxn * 2];

bool cmp(line xx , line yy){
	return xx.x < yy.x;
}
#define ls o * 2
#define rs o * 2 + 1
long long sum[maxn * 4] , tag[4 * maxn] , L[4 * maxn] , R[4 * maxn];
//sum表示这个节点维护的长度和
//tag表示这个节点维护的线段被扫到的给完全覆盖几次
//L,R显然就是左右端点,这里是为了方便pushup
void build(int o,int l,int r){
	L[o] = l , R[o] = r;
	if(l == r) return;
	int mid = (l + r) >> 1;
	build(ls,l,mid);
	build(rs,mid + 1 , r);	
}

void pushup(int o){
	int a = L[o] , b = R[o];
	if(tag[o]) sum[o] = Y[b + 1] - Y[a];//完全被覆盖用左右儿子去更新就有可能会重合 
	else sum[o] = sum[2*o] + sum[2*o+1];
	return;
}

void change(int o,int l,int r,int c,int d,int k){
	if(Y[r + 1] <= c || Y[l] >= d) return;//特殊判断一下
	if(c <= Y[l] && Y[r + 1] <= d){//注意这里是离散化的l,r 
		tag[o] += k;
		pushup(o);	//cout<<Y[l]<<"  "<<Y[r+1]<<"  "<<c<<" "<<d<<"   ";
		//cout<<sum[o]<<endl;
		return;
	}
	int mid = (l + r) >> 1;
	if(Y[mid] >= c) change(ls,l,mid,c,d,k);
	if(Y[mid] < d) change(rs,mid+1,r,c,d,k);
	pushup(o);
}

int main(){
	scanf("%d",&n);
	for(int i = 1; i <= n; i++){
		scanf("%d%d%d%d",&x,&y,&x_,&y_);
		Y[2 * i - 1] = y , Y[2 * i] = y_;
		sea[2 * i - 1] = (line){y , y_ , x , 1};//第一条是加入
		sea[2 * i] = (line){y , y_ , x_ , -1}; //去除
	}
	n = n * 2;
	sort(sea + 1, sea + 1 + n , cmp);
	sort(Y + 1 , Y + 1 + n);
	int tail = unique(Y + 1 , Y + 1 + n) - (Y + 1);
	build(1,1,tail - 1);//这里我们用左代表Y[l]有代表Y[r + 1]这样就避免了不更新的状况
	long long ans = 0;
	for(int i = 1 ; i < n ; i++){
		change(1,1,tail - 1,sea[i].l,sea[i].r,sea[i].mark);
		ans += sum[1] * (sea[i + 1].x - sea[i].x) * 1ll;
	}
	cout << ans <<"\n";
	return 0;	
}

杂题

一P1502 窗口的星星

思路比较清晰的一道题,主要是我们做题时要带脑子,不要直接套板子

每一个星星我们可以把他扩大窗户倍数,之后就课以变成一个矩形,只要这个窗子的右顶点在这个矩形内就欧克,之后就是扫描线维护区间面积max,比较简单,但是还是要注意两个点
1.边界不算,其实我们只用把左边和右边都减去0.5这样就好了,因为星星的坐标是没有浮点数的,
2.线段树上的l,r 其实维护的是l,r+1。这里我们这么做对吗?想一下我们为什么在扫描线的模板上要用r+1去覆盖,是不是因为我们这里怕的是一个节点本来代表的是2,3,而他的l,r确实3,3,所以我们要用这个。这里我们是不是相当于对一个线段覆盖,转化为了对于一堆点的覆盖,不需要知道长度,所以说我们是不是就直接l,r就好了

code

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值