2021牛客暑期多校训练营6 H. Hopping Rabbit(扫描线)

链接:https://ac.nowcoder.com/acm/contest/11257/H
来源:牛客网

题目描述

Little Rabbit loves hopping. He always hops around on the grassland. But dangers are lurking there --- the hunters have set nn traps. Once Little Rabbit falls into a trap, his life will be threatened. Therefore, Little Rabbit has to keep away from those traps.

Let's use two-dimensional Cartesian coordinate system to describe the grassland. We can regard Little Rabbit as a point, and regard traps as rectangles on the plane. The sides of the rectangles are parallel to the coordinate axes. We use two points to describe a rectangle --- the lower left point (x1,y1)(x1,y1) and the upper right point (x2,y2)(x2,y2) (x1<x2x1<x2, y1<y2y1<y2, x1,y1,x2,y2x1,y1,x2,y2 are all integers).

Little Rabbit can hop in the direction of xx-axis or yy-axis. The length of each hop is dd. Formally, if Little Rabbit locates at (x,y)(x,y), he can then hop to (x−d,y)(x−d,y), (x+d,y)(x+d,y), (x,y−d)(x,y−d) or (x,y+d)(x,y+d).

Little Rabbit wants to find an initial position (x0+0.5,y0+0.5)(x0+0.5,y0+0.5) (x0x0 and y0y0 are integers) so that when he starts to hop from this point, no matter how he hops, he will never fall into a trap. Please help Little Rabbit to find such a point.

Please note that during a hop, Little Rabbit is in the air and won't fall into a trap. Little Rabbit will fall into a trap only if his landing point is in a trap.

输入描述:

The first line contains two integers nn and dd (1≤n,d≤1051≤n,d≤105) --- the number of traps and the length of each hop.


Then in the next nn lines, each line contains four integers x1,y1,x2,y2x1,y1,x2,y2 (−109≤x1,y1,x2,y2≤109−109≤x1,y1,x2,y2≤109, x1<x2x1<x2, y1<y2y1<y2) --- the lower left point and the upper right point of the rectangle.

It's possible that the rectangles will intersect with each other.

输出描述:

If there exist valid answers, output YES in the first line. Then output two integers x0,y0x0,y0 (−109≤x0,y0≤109−109≤x0,y0≤109) separated by a space in the second line, indicating a valid initial position (x0+0.5,y0+0.5)(x0+0.5,y0+0.5). If there are multiple answers, output any.

If there are no valid answers, output NO in a single line.

示例1

输入

复制

2 2
1 1 2 2
2 2 3 3

输出

复制

YES
3 2

示例2

输入

复制

1 1
1 1 2 2

输出

复制

NO

因为从横坐标x, x + d, x + 2d....以及从坐标y, y + d, y + 2d...这些位置跳的结果都是相同的(即题解说的“能到的位置是周期重复的”),因此可以看做把整个平面用间隔为d的横纵网格线隔开,为方便起见设x轴和y轴也是网格线。那么整个平面就被分为若干个小正方形。把这些正方形摞在一起,如果还有空白区域的话说明是有解的。这就是经典的矩形求并的扫描线算法。由于这个题给的矩形坐标都是整数,因此需要“化点为块”,考虑边界问题。设矩形的真实坐标为(x1, y1)(x2, y2),那么转化为扫描线算法的线段就是(x1, y1, y2 - 1, 1)(左边界)以及(x2, y1, y2 - 1, -1)(右端点),这里的四元组定义参考蓝书线段树一节。因为d的范围不大,考虑用桶存x轴每个位置对应的四元组。依次遍历0到d - 1,对于每个位置先取出其所有四元组进行change操作,然后查询目前是否有位置没有被覆盖,如果有的话则遍历这一列,枚举每个位置找到没有被覆盖的那一个作为答案即可。这样就需要我们的线段树支持区间修改以及区间查询最小值。那么就剩下最后一个问题:如何把整个矩形分割?注意到,不论矩形有多大是什么形状,最终起作用的至多不超过四块。举个例子,设d = 2, 矩形为(1, 1)(5, 3),此时矩形被网格线分成了六块,但实际起作用的只有中间的两块(剩下的分别被这两块覆盖了),因此分类讨论一下即可。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
int n;
ll d;
struct line {//表示为线的矩形的左右边界
	ll x, y1, y2;
	ll add;
};
bool cmp(line a, line b) {
	return a.x < b.x;
}
struct SegmentTree {
	ll p, l, r, val, mn, add;//mn是子区间覆盖次数最小值,add是延迟标记
} t[200005 * 4];
void build(ll p, ll l, ll r) {
	t[p].l = l, t[p].r = r;
	t[p].mn = 0;
	if(l == r) {
		return;
	}
	ll mid = (l + r) >> 1;
	build(2 * p, l, mid);
	build(2 * p + 1, mid + 1, r);
	return;
}
void spread(ll p) {
	if(t[p].add != 0) {
		t[2 * p].mn +=t[p].add;
		t[2 * p + 1].mn +=t[p].add;
		t[2 * p].add += t[p].add;//!!!!!!
		t[2 * p + 1].add += t[p].add;
		t[p].add = 0;
	}
}
void change(ll p, ll L, ll R, ll v) {
	if(t[p].l >= L && t[p].r <= R) {
		t[p].mn += v;//整个已经覆盖了
		t[p].add += v;
		return;
	}
	spread(p);
	ll mid = (t[p].l + t[p].r) >> 1;
	if(L <= mid) change(2 * p, L, R, v);
	if(R > mid) change(2 * p + 1, L, R, v);
	t[p].mn = min(t[2 * p].mn, t[2 * p + 1].mn);
}
ll ask(ll p, ll L, ll R) {
	if(t[p].l >= L && t[p].r <= R) {
		return t[p].mn;
	}
	spread(p);
	int mid = (t[p].l + t[p].r) >> 1;
	ll ret = 0x3f3f3f3f;
	if(L <= mid) ret = min(ret, ask(2 * p, L, R));
	if(R > mid) ret = min(ret, ask(2 * p + 1, L, R));
	return ret;
}
vector<line> Line[100005];//如果写成vector<vector<line> > Line(d + 5)会报非常奇怪的错
void add(ll x1, ll y1, ll x2, ll y2) {////注意这里(0, 0)(0, 0)实际上代表(0, 0)(1, 1)这个矩形
	Line[x1].push_back({x1, y1, y2, 1});
	Line[x2 + 1].push_back({x2 + 1, y1, y2, -1});//注意这里是x2 + 1 类似差分的思想
}
int main() {
	cin >> n >> d;
	for(int i = 1; i <= n; i++) {
		ll x1, y1, x2, y2;
		cin >> x1 >> y1 >> x2 >> y2;
		x2--, y2--;//点化为块
		//下面两句画图理解一下
		if(x2 - x1 + 1 >= d) x1 = 0, x2 = d - 1; 
		if(y2 - y1 + 1 >= d) y1 = 0, y2 = d - 1;
		x1 = (x1 % d + d) % d, x2 = (x2 % d + d) % d, y1 = (y1 % d + d) % d, y2 = (y2 % d + d) % d;//矩形取模
		if(x2 >= x1) {//不要忘记取等号 //把一个矩形分为1、2或4个矩形
			if(y2 >= y1) {
				add(x1, y1, x2, y2);
			} else {
				add(x1, y1, x2 ,d - 1);
				add(x1, 0, x2 ,y2);
			}
		} else {
			if(y2 >= y1) {
				add(x1, y1, d - 1, y2);
				add(0, y1, x2, y2);
			} else {
				add(x1, y1, d - 1, d - 1);
				add(0, y1, x2, d - 1);
				add(x1, 0, d - 1, y2);
				add(0, 0, x2, y2);
			}
		}
	}
	build(1, 0, d - 1);
	ll ansx = -1, ansy = -1;
	for(ll i = 0; i < d; i++) {
		if(ansx != -1) break;
		for(auto l : Line[i]) {
			change(1, l.y1, l.y2, l.add);//必须先修改再查询
		}
		if(ask(1, 0, d - 1) == 0) {//如果有没有被覆盖的位置
			for(ll j = 0; j <= d - 1; j++) {
				if(ask(1, j, j) == 0) {
					ansx = i, ansy = j;
					break;
				}
			}
		} 
	}
	if(ansx != -1) {
		puts("YES");
		cout << ansx << " " << ansy << endl;
	} else {
		puts("NO");
	}
	return 0;
}

// 3 4
// 0 0 2 2
// 1 0 3 4
// 0 2 4 4