[COCI2018-2019#2] Sunčanje-CDQ分治

矩阵覆盖问题-CDQ分治

[COCI2018-2019#2] Sunčanje

题目描述

Slavko 做了一个不寻常的梦。在一个晴朗的早上, N N N 个白色的矩形一个接着一个爬上了 Slavko 家的屋顶,并在屋顶上晒太阳。每个矩形在屋顶都选定了一个位置,使得它的边与屋顶的棱角平行。有些矩形可能会覆盖在其它矩形所在的位置上。每个矩形的长、宽分别为 A i , B i A_i,B_i Ai,Bi,其与屋顶左方和下方的棱角的距离分别为 X i , Y i X_i,Y_i Xi,Yi

日落后,矩形们从屋顶上下来,并睡了一觉。次日,它们发现,有些矩形变成了黄色,而有些仍为白色。变为黄色的矩形都是完全暴露在阳光下的。

请判断每个矩形是否变为了黄色。

输入格式

第一行输入正整数 N N N,表示矩形的个数。

接下来的 N N N 行,每行输入整数 X i , Y i , A i , B i X_i,Y_i,A_i,B_i Xi,Yi,Ai,Bi。输入顺序与登上屋顶的顺序一致。

输出格式

输出 N N N 行。其中,若第 i i i 个矩形变为黄色,则在第 i i i 行输出 DA,否则在该行输出 NE

样例 #1

样例输入 #1

5
1 1 4 2
6 1 1 1
2 2 2 3
3 4 3 2
4 0 1 2

样例输出 #1

NE
DA
NE
DA
DA

样例 #2

样例输入 #2

3
3 3 1 1
2 2 3 3
1 1 5 5

样例输出 #2

NE
NE
DA

提示

样例 1 解释

矩形 1 , 3 1,3 1,3 没有完全暴露在阳光下,因而它们没有变为黄色:

数据规模与约定

对于 10 % 10\% 10% 的数据, N ≤ 1 0 4 N \le 10^4 N104

对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 1 0 5 1 \le N \le 10^5 1N105 0 ≤ X i , Y i ≤ 1 0 9 0 \le X_i,Y_i \le 10^9 0Xi,Yi109 1 ≤ A i , B i ≤ 1 0 9 1 \le A_i,B_i \le 10^9 1Ai,Bi109

说明

本题分值按 COCI 原题设置,满分 130 130 130

题目译自 COCI2018-2019 CONTEST #2 T5 Sunčanje

分析

我们记矩阵序列为 A A A ,其中 A [ i ] = ( x 1 , y 1 , x 2 , y 2 ) ( x 1 ≤ x 2 , y 1 ≤ y 2 ) A[i]=(x_1,y_1,x_2,y_2)(x_1\le x_2,y1\le y_2) A[i]=(x1,y1,x2,y2)(x1x2,y1y2) ,为了方便,我们将 A [ i ] A[i] A[i] x 1 x_1 x1 写作 A [ i , x 1 ] A[i,x_1] A[i,x1] ,其余类似

那么矩阵 i i i 被矩阵 j j j 覆盖需要满足以下条件

{ i < j { A [ i , x 1 ] ≤ A [ j , x 2 ] A [ j , x 1 ] ≤ A [ i , x 2 ] { A [ i , y 1 ] ≤ A [ j , y 2 ] A [ j , y 1 ] ≤ A [ i , y 2 ] \left\{ \begin{aligned} i<j\\ \left\{ \begin{aligned} A[i,x_1]&\le A[j,x_2] \\ A[j,x_1]&\le A[i,x_2] \\ \end{aligned} \right.\\ \left\{ \begin{aligned} A[i,y_1]&\le A[j,y_2] \\ A[j,y_1]&\le A[i,y_2] \\ \end{aligned} \right. \end{aligned} \right. i<j{A[i,x1]A[j,x1]A[j,x2]A[i,x2]{A[i,y1]A[j,y1]A[j,y2]A[i,y2]

(在这里有一个细节,就是有可能一个矩形的右边和一个矩形的左边在同一直线上(亦或者一个矩形的上边和一个矩形的下边在同一直线上),但此时没有交点却仍然会被累加进入答案,故为了解决这个问题,可以把右上角整体缩一格,也即右上角的横纵坐标全部-1(这就是部分同志最后几个点过不了的原因));

考虑如何求解
这个玩意类似于偏序问题,考虑使用 CDQ 分治

若按照时间轴分治,则设分治两区间为 [ l , m i d ] , [ m i d + 1 , r ] [l,mid],[mid+1,r] [l,mid],[mid+1,r]

此时 [ l , m i d ] [l,mid] [l,mid] 的时间都早于 [ m i d + 1 , r ] [mid+1,r] [mid+1,r]

此时我们需要考虑后半区间对前半区间的影响,先用常规双指针套路维护限制条件 A [ j , x 1 ] ≤ A [ i , x 2 ] A[j,x_1]\le A[i,x_2] A[j,x1]A[i,x2]

将区间 [ l , m i d ] [l,mid] [l,mid] 按照 x 2 x_2 x2 递增排序,区间 [ m i d + 1 , r ] [mid+1,r] [mid+1,r] 按照 x 1 x_1 x1 递增排序,设两个指针分别为 L , R L,R L,R

采用双指针扫描,则 [ m i d + 1 , R ] [mid+1,R] [mid+1,R] 的矩形都满足对于 L L L 来说 A [ L , x 2 ] ≥ A [ k , x 1 ] A[L,x_2]\ge A[k,x_1] A[L,x2]A[k,x1] ,考虑判定在 [ m i d + 1 , R ] [mid+1,R] [mid+1,R] 中有无矩形可以覆盖 L L L

按照 C D Q CDQ CDQ 分治的套路我们需要数据结构来维护剩余的几个条件

{ A [ i , x 1 ] ≤ A [ j , x 2 ] A [ i , y 1 ] ≤ A [ j , y 2 ] A [ j , y 1 ] ≤ A [ i , y 2 ] \left\{ \begin{aligned} A[i,x_1]&\le A[j,x_2] \\ A[i,y_1]&\le A[j,y_2] \\ A[j,y_1]&\le A[i,y_2] \\ \end{aligned} \right. A[i,x1]A[i,y1]A[j,y1]A[j,x2]A[j,y2]A[i,y2]

这里由于限制条件就是要求矩形横向有交点,竖向有交点,而对于竖向有交点,可以转化为线段有交问题,考虑使用维护线段的数据结构——线段树

那,我们对区间 [ A [ i , y 1 ] , A [ i , y 2 ] ] [A[i,y_1],A[i,y_2]] [A[i,y1],A[i,y2]] 进行修改,只需要我们能够判定在区间 [ A [ L , y 1 ] , A [ L , y 2 ] ] [A[L,y_1],A[L,y_2]] [A[L,y1],A[L,y2]] 中有无大于等于 A [ L , x 1 ] A[L,x_1] A[L,x1] 的元素,这启发我们的线段树维护区间最大值,假设我们将区间 [ l ′ , r ′ ] [l',r'] [l,r] 的最大值记为 g [ l ′ , r ′ ] g[l',r'] g[l,r] ,记 f [ i ] f[i] f[i] 表示第 i i i个矩形是否被覆盖

那么就有 f [ A [ L , i d ] ] ∣ = A [ L , x 1 ] ≤ g [ A [ L , y 1 ] , A [ L , y 2 ] ] f[A[L,id]]|=A[L,x_1]\le g[A[L,y_1],A[L,y_2]] f[A[L,id]]=A[L,x1]g[A[L,y1],A[L,y2]]

统计完了之后将线段树全部清空为-1即可

最后,我们再来梳理一遍这个过程

  1. 离散化坐标
  2. 在CDQ分治中
  3. 左半区间按照 x 2 x_2 x2排,右半区间按 x 1 x_1 x1
  4. 双指针扫描,使得 A [ L , x 2 ] ≥ A [ R , x 1 ] A[L,x_2]\ge A[R,x_1] A[L,x2]A[R,x1]
  5. 在扫描的过程中不断在线段树的区间 [ A [ R , y 1 ] , A [ R , y 2 ] ] [A[R,y_1],A[R,y_2]] [A[R,y1],A[R,y2]]上更新区间最大值
  6. 对于 L L L查询区间 [ A [ L , x 1 , x 2 ] ] [A[L,x_1,x_2]] [A[L,x1,x2]]是否存在大于 A [ L , x 1 ] A[L,x_1] A[L,x1]的值,存在也就表示 L L L被覆盖
  7. 清空线段树

时间复杂度是 O ( n log ⁡ 2 n ) O(n\log^2n) O(nlog2n),线段树常数大得一批,怪不得时间要求4s

#include<iostream>
#include<cstdio>
#include<algorithm>
#define scanf scanf_s
using namespace std;
#define N 1000050
struct node {
	int id, x1, x2, y1, y2;
}a[N];//矩阵
int b[N], c[N], cnt;//离散化
int f[N], n, m;
struct seg_tree {
	int mx, tag, lazy, l, r;
}t[N << 2];//线段树
#define lc x<<1
#define rc x<<1|1
void build(int l, int r, int x) {
	t[x] = { 0,0,-1,l,r };
	if (l == r)return;
	int mid = l + r >> 1;
	build(l, mid, lc);
	build(mid + 1, r, rc);
}
inline void pushup(int x) {
	t[x].mx = max(t[lc].mx, t[rc].mx);
}
inline void pushdown(seg_tree& a, seg_tree& b, seg_tree& c) {
	if (a.lazy != -1) {
		b.tag = c.tag = 0;
		b.mx = c.mx = 0;
		b.lazy = c.lazy = 0;
		a.lazy = -1;
	}
	b.tag = max(b.tag, a.tag);
	c.tag = max(c.tag, a.tag);
	b.mx = max(b.mx, a.tag);
	c.mx = max(c.mx, a.tag);
	a.tag = 0;
	return;
}
inline void pushdown(int x) {
	pushdown(t[x], t[lc], t[rc]);
}
void update(int x, int l, int r, int k) {
	if (l <= t[x].l && t[x].r <= r) {
		t[x].mx = max(t[x].mx, k);
		t[x].tag = max(t[x].tag, k);
		return;
	}
	pushdown(x);
	int mid = t[x].l + t[x].r >> 1;
	if (l <= mid)update(lc, l, r, k);
	if (mid < r)update(rc, l, r, k);
	pushup(x);
}
int find(int x, int l, int r) {
	if (l <= t[x].l && t[x].r <= r) {
		//		printf("%d %d %d %d %d\n", t[x].lazy, t[x].tag, t[x].l, t[x].r, t[x].mx);
		return t[x].mx;
	}
	int ans = -1, mid = t[x].l + t[x].r >> 1;
	pushdown(x);
	if (l <= mid)ans = max(ans, find(lc, l, r));
	if (mid < r)ans = max(ans, find(rc, l, r));
	pushup(x);
	return ans;
}
#undef lc
#undef rc
void init() {
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) {
		int A, B, c, d;
		scanf("%d%d%d%d", &A, &B, &c, &d);
		c += A - 1, d += B - 1;
		a[i] = { i,A,c,B,d };
		b[++cnt] = A, b[++cnt] = B, b[++cnt] = c, b[++cnt] = d;
	}
	sort(b + 1, b + cnt + 1);
	cnt = unique(b + 1, b + cnt + 1) - b - 1;
	//	printf("%d\n", cnt);
	for (int i = 1; i <= n; i++) {
		a[i].x1 = lower_bound(b + 1, b + cnt + 1, a[i].x1) - b;
		a[i].y1 = lower_bound(b + 1, b + cnt + 1, a[i].y1) - b;
		a[i].x2 = lower_bound(b + 1, b + cnt + 1, a[i].x2) - b;
		a[i].y2 = lower_bound(b + 1, b + cnt + 1, a[i].y2) - b;
		//		printf("%d %d %d %d\n", a[i].x1, a[i].y1, a[i].x2, a[i].y2);
	}
}
bool cmp1(node a, node b) {
	return a.x1 < b.x1;
}
bool cmp2(node a, node b) {
	return a.x2 < b.x2;
}
void cdq(int l, int r) {
	if (l == r)return;
	int mid = l + r >> 1;
	cdq(l, mid);
	cdq(mid + 1, r);
	int L = l, R = mid + 1;
	sort(a + l, a + mid + 1, cmp2);
	sort(a + R, a + r + 1, cmp1);
	//	for (int i = l; i <= r; i++)printf("%d ", a[i].id);
	//	puts("");
	for (; L <= mid; L++) {
		while (a[R].x1 <= a[L].x2 && R <= r) {
			update(1, a[R].y1, a[R].y2, a[R].x2);
			R++;
		}
		f[a[L].id] |= find(1, a[L].y1, a[L].y2) >= a[L].x1;
		//	if (find(1, a[L].y1, a[L].y2) >= a[L].x1) {
		//		printf("%d %d %d\n", a[L].id, find(1, a[L].y1, a[L].y2), L-l);
		//	}
	}
	t[1].tag = t[1].mx = t[1].lazy = 0;
	pushdown(1);
	//	puts("Cleaded");
}
int main() {
	init();
	build(1, cnt << 1, 1);
	cdq(1, n);
	for (int i = 1; i <= n; i++) {
		if (f[i])puts("NE");
		else puts("DA");
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值