「洛谷 P8858」折线 - 贪心 + 树状数组 + 二分

题意描述

平面直角坐标系的第一象限内有一块左下角为 \((0,0)\) 右上角为 \((10^{100},10^{100})\) 的矩形区域,区域内有正偶数个整点,试求出这样一条从 \((0,0)\) 出发,到 \((10^{100},10^{100})\) 的在区域内部的折线:

  • 折线的每一部分都平行于 \(x\) 轴或 \(y\) 轴。
  • 折线不能经过给定的整点。
  • 折线将整块区域分成包含给定整点个数相等的两块。
  • 折线拥有尽可能少的折点。

可以证明一定存在一条满足限制的折线,你只需要输出满足限制的折线的折点数即可。

注意折点的坐标可以不是整数。

每组测试点有 \(T\) 组数据,对于所有测试点,\(1 \leq T \leq 10^4\)\(1 \leq \sum n \leq 5 \times 10^5\)\(1 \leq n \leq 10^5\)\(1 \leq x_i,y_i \leq n\),保证 \(n\) 为正偶数,每组数据中不存在两个坐标相同的整点。

解题思路

首先我们可以证明出一条定理:答案必定大于等于 \(2\) 且小于等于 \(4\)。首先由于点都在 \((1 \sim 100, 1 \sim 100)\) 之间,于是为了在 \((1 \sim 100, 1 \sim 100)\) 之间平分点,我们至少要在这里转折一次。而在这区间之外无论如何均只需转向一次就可平分面积。于是答案必定大于等于 \(2\)。又显然我们可以通过至多 \(3\) 次转折分隔点,加上分隔面积总共 \(4\) 次,于是答案小于等于 \(4\)

于是我们可以分类讨论情况答案是否为 \(2\)\(3\)\(4\) 即可。目前我们从横行考虑。纵行翻转一下坐标同理。

答案为 \(2\) 时很好判断,显然我们只需要判断横行上下点的数目是否相等即可。可用树状数组解决。

答案为 \(3\) 时,我们可推出情况为选定一个横行 \(x\) 与纵行 \(y\),满足 \(a \le x\)\(y \le b\) 的点 \((a, b)\) 的个数为 \(\frac{n}{2}\)。于是我们可将每个点按照先 \(x\)\(y\) 的优先级从小到大排序,然后对于每一行二分求出是否可找出满足条件的 \(y\) 即可。

其余情况答案即为 \(4\),单次询问时间复杂度为 \(O(n \log^2 n)\)

代码演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include <cstdio>
#include <vector>
#include <algorithm>

struct BinaryIndexedTree {
int n;
std::vector<int> a;

void init(int n) {
this->n = n;
a.resize(n + 1);
std::fill(a.begin(), a.end(), 0);
}

static int lowbit(int x) {
return x & -x;
}

void update(int pos, int delta) {
for (; pos <= n; pos += lowbit(pos)) a[pos] += delta;
}

int query(int pos) {
int res = 0;
for (; pos; pos -= lowbit(pos)) res += a[pos];
return res;
}
} bit;

void solve() {
int n;
scanf("%d", &n);

std::vector<std::pair<int, int>> a[2];
for (int i = 0; i < 2; i++) a[i].resize(n);
for (int i = 0; i < n; i++) {
scanf("%d %d", &a[0][i].first, &a[0][i].second);
a[1][i] = std::make_pair(a[0][i].second, a[0][i].first);
}
std::sort(a[0].begin(), a[0].end());
std::sort(a[1].begin(), a[1].end());

for (int i = 0; i < 2; i++) {
bit.init(n);
for (int j = 0; j < n; j++) bit.update(a[i][j].first, 1);
for (int j = 1; j <= n; j++) {
if (bit.query(j) == n / 2) {
puts("2");
return;
} else if (bit.query(j) > n / 2) break;
}
}

for (int i = 0; i < 2; i++) {
bit.init(n);
for (int j = 0; j < n; j++) {
bit.update(a[i][j].second, 1);
if (j == n - 1 || a[i][j + 1].first != a[i][j].first) {
int l = 0, r = n;
while (l < r) {
int mid = l + (r - l + 1) / 2;
if (bit.query(mid) <= j + 1 - n / 2) l = mid;
else r = mid - 1;
}
if (bit.query(l) == j + 1 - n / 2) {
puts("3");
return;
}
}
}
}

puts("4");
}

int main() {
int t;

scanf("%d", &t);

while (t--) solve();

return 0;
}