[CF983D]Arkady and Rectangles

本文介绍了一种解决三维偏序问题的方法,通过扫描线算法和线段树数据结构,有效地处理了时间、横坐标和纵坐标的区间操作,实现了加入和删除矩形的功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

传送门 to luogu

思路

看上去像一个 三维偏序 问题,即 x , y , t i m e x,y,time x,y,time 三个维度。请忘掉 k - d k\text-d k-d 树,因为 我真的不会

想办法用扫描线干掉一维。对 t i m e time time 扫描线,恕我直言,完全是脑子瓦特了。因为 t i m e time time 是一个简单的维度。与之相对的, x x x y y y 都是区间操作,更复杂。

不妨对 x x x 扫描线。那么 y y y 需要一个数据结构解决一维区间问题。优先选择线段树。那么线段树的节点就需要维护一些与 t i m e time time 有关的值,来支持下面两种操作:

  • 加入一个矩形,并查看它是否能被看到。
  • 删除一个矩形,并检查这是否让一些矩形露了出来。

先解决第一个操作。一个矩形什么情况下会被看见呢?其 t i m e time time 超过 区间内能看到的矩形的 min ⁡ t i m e \min time mintime 即可。这东西感觉很好维护,是吧?——它怎么支持删除操作呢?

不妨请出我们的老朋友,标记永久化。所有标记都是 “有一个矩形完全覆盖了这个区间”,维护的 m n mn mn子树内的 min ⁡ t i m e \min time mintime,查询时是自顶向下,顺便把一条链上的信息叠加起来就行。标记可以用 s e t \rm set set 存储。更新都用最大值,因为 m n mn mn 是子树内 可看到的颜色 的最小值。

现在考虑解决第二个问题。其实一个节点只在乎子节点和父节点的信息。在删除一个节点的时候,其父节点会即刻更新,不用担心,问题只在于子节点。打一个懒标记就可以了,表示 “把子树内的所有点,按照根节点到当前点的 l a z y = max ⁡ t i m e lazy=\max time lazy=maxtime 来更新一次” 即可。这个标记可以合并,也可以下传,完全没问题。

复杂度呢?加入一个矩形,相当于线段树套平衡树,所以复杂度是 O ( n log ⁡ 2 n ) \mathcal O(n\log^2 n) O(nlog2n) 的。删除一个矩形,显然不能访问所有标记。只能再开一个 s e t \rm set set 存储 对应到这个节点的没露出的矩形,那么每个标记会用 O ( log ⁡ n ) \mathcal O(\log n) O(logn) 的时间删掉,一共 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn) 个标记,总复杂度
O ( n log ⁡ 2 n ) \mathcal O(n\log^2 n) O(nlog2n)
由于只需要取最大值,用堆来实现是一样的。

代码

竟然是……离散化!打错了!这种问题一定要把 y y y y − 1 y-1 y1 都丢进去!

否则,一个覆盖了 y 1 y_1 y1,另一个覆盖了 y 2 y_2 y2,只要二者中间没有别的 y y y,就会被判定为 “相邻” 了!要加入 y 2 − 1 y_2-1 y21 来避免。

另:我的写法中 p u s h D o w n \rm pushDown pushDown 是要管当前节点的,所以叶子节点也必须清空标记(不能直接 r e t u r n \rm return return 走人)。

#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}

const int infty = (1<<30)-1;
struct BestHeap{
	priority_queue< int > pq, bad;
	void push(int x){ pq.push(x); }
	void erase(int x){ bad.push(x); }
	void pop(){ pq.pop(); }
	void clear(){
		while(!pq.empty()) pq.pop();
		while(!bad.empty()) bad.pop();
	}
	bool empty(){
		while(!pq.empty() && !bad.empty())
			if(pq.top() == bad.top())
				pq.pop(), bad.pop();
			else if(bad.top() > pq.top())
				bad.pop(); // useless
			else break; // no more
		return pq.empty();
	}
	int top(){
		if(!empty()) return pq.top();
		return -infty; // minimum
	}
};

const int MaxN = 400005;
int n, m; bool good[MaxN];
namespace SgTree{
	const int MaxM = MaxN<<2;
	BestHeap all[MaxM], s[MaxM];
	int lazy[MaxM], mn[MaxM];
	void pushUp(int o,int l,int r){
		if(l != r) // not a leaf
			mn[o] = min(mn[o<<1],mn[o<<1|1]);
		else mn[o] = 0; // background
		mn[o] = max(mn[o],all[o].top());
	}
	void pushDown(int o,int l,int r){
		if(lazy[o] > n) return ;
		int t = max(lazy[o],mn[o]);
		for(; s[o].top()>=t; s[o].pop())
			good[s[o].top()] = true;
		if(l != r){
			t = max(lazy[o],all[o].top());
			lazy[o<<1] = min(lazy[o<<1],t);
			lazy[o<<1|1] = min(lazy[o<<1|1],t);
		}
		lazy[o] = infty; // ineffective
	}
	void add(int ql,int qr,int qv,int pre=0,int o=1,int l=1,int r=m){
		pushDown(o,l,r); // important
		if(ql <= l && r <= qr){
			if(max(pre,mn[o]) <= qv)
				good[qv] = true;
			else s[o].push(qv);
			all[o].push(qv);
			return pushUp(o,l,r); // update info
		}
		int mid = (l+r)>>1; pre = max(pre,all[o].top());
		if(ql <= mid) add(ql,qr,qv,pre,o<<1,l,mid);
		if(mid < qr) add(ql,qr,qv,pre,o<<1|1,mid+1,r);
		return pushUp(o,l,r); // update info
	}
	void del(int ql,int qr,int qv,int pre=0,int o=1,int l=1,int r=m){
		pushDown(o,l,r); // important
		if(ql <= l && r <= qr){
			s[o].erase(qv), all[o].erase(qv);
			lazy[o] = pre; return pushUp(o,l,r);
		}
		int mid = (l+r)>>1, t = max(pre,all[o].top());
		if(ql <= mid) del(ql,qr,qv,t,o<<1,l,mid);
		if(mid < qr) del(ql,qr,qv,t,o<<1|1,mid+1,r);
		pushUp(o,l,r); t = max(pre,mn[o]);
		for(; s[o].top()>=t; s[o].pop())
			good[s[o].top()] = true;
	}
	void clear(int o=1,int l=1,int r=m){
		lazy[o] = infty, mn[o] = 0;
		all[o].clear(), s[o].clear();
		if(l == r) return ;
		clear(o<<1,l,(l+r)>>1);
		clear(o<<1|1,(l+r)/2+1,r);
	}
}

struct Command{
	int opt, ly, ry, id, x;
	bool operator < (const Command &t) const {
		if(x != t.x) return x < t.x;
		if(opt != t.opt)
			return opt > t.opt;
		return opt*id > opt*t.id;
	}
};
Command q[MaxN];

int item[MaxN];
int LiSan(int* l,int* r){
	memcpy(item,l,(r-l)<<2);
	sort(item,item+(r-l));
	int *q = unique(item,item+(r-l));
	for(int* i=l; i!=r; ++i)
		*i = lower_bound(item,q,*i)-item+1;
	return q-item; // cnt of distinct
}

int tmpy[MaxN];
int main(){
	n = readint();
	for(int i=0; i<n; ++i){
		q[i<<1].x = readint();
		tmpy[i<<1] = readint();
		q[i<<1|1].x = readint();
		tmpy[i<<1|1] = readint()-1;
		tmpy[(n<<1)+i] = tmpy[i<<1|1]+1;
	}
	m = LiSan(tmpy,tmpy+n*3);
	for(int i=0; i<n; ++i){
		q[i<<1].opt = 1, q[i<<1|1].opt = -1;
		q[i<<1].ly = q[i<<1|1].ly = tmpy[i<<1];
		q[i<<1].ry = q[i<<1|1].ry = tmpy[i<<1|1];
		q[i<<1].id = q[i<<1|1].id = i+1;
	}
	sort(q,q+(n<<1)); // sort them
	SgTree::clear();
	for(int i=0; i<(n<<1); ++i){
		if(q[i].opt == 1)
			SgTree::add(q[i].ly,q[i].ry,q[i].id);
		else SgTree::del(q[i].ly,q[i].ry,q[i].id);
	}
	int ans = 1; // background
	for(int i=1; i<=n; ++i)
		ans += good[i];
	printf("%d\n",ans);
	return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值