抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

题意:

在 r×c 的网格上有 n 个点。求至少包含一个点的矩形个数。

r,c40000,n100000r, c \leq 40000, n \leq 100000 所有点随机生成。

题解:

无论是正向用单调栈查找矩形,还是反向查找不包含点的矩形复杂度都是 O(rc)O(rc) 的(谁想卡常吗? 233)

下面我们考虑这个问题的反面:不包含任何点的矩形个数(下面将这些点称为障碍点。)

但是这道题的特殊性质在于障碍点个数较少,且随机。

(然而对着这两个性质看并看不出来什么东西。。。)

考虑这个问题的经典解法:

考虑每一行作为矩形的下边界时对答案的贡献,显然我们只需要考虑每一列向上一直到障碍点的格子对答案的贡献。

可以发现只需要确定左上和右上点,就可以唯一确定矩形。

因为一对点 (A,B)(A, B) 合法当且仅当它们在同一行且与底边围成的矩形中不含障碍点。

这时我们发现,可以将这些格子分成若干个小矩形(需要一张图来表述),则每一个小矩形内每一行的格子两两组合均合法。则对答案贡献为 ( 对于 w×h 的矩形 )w(w1)h2\frac {w(w-1)h} {2}.

如果将相邻的矩形相连,可以发现它是个二叉树结构,且左右儿子为一个障碍点左右的矩形。

因为障碍点随机,则可以证(脑)明(补)二叉树的树高均摊 O(logn)O(\log n).

那么我们只需要在向下扫描的时候维护这棵二叉树即可。

考虑每次将枚举的边下移时,首先肯定将根矩形的高度 ++。

然后对于这一行的每一个障碍点分别更新树结构,同时更新答案。每次更新是 O(height)O(\text{height}) 的,所以总复杂度 O(nlogn)O(n \log n)

考虑如何更新树结构,考虑所有包含障碍点所在列的矩形(显然形成了一条链),将它们拆成两个矩形,然后与父节点相连。

这里可以发现,拆分后有一侧的矩形一定能够与其子矩形合并成一个大矩形,可以将它们合并。(不知道这不加能不能过。。)

除此之外,还可以使用 Treap 来维护这样的矩形。

Code

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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
int x[MAXN], y[MAXN], r, c, n;
namespace solver1 {
struct Node {
int l, r, h;
Node *ls, *rs;
long long cal() {
return (long long) (r - l + 1) * (r - l + 2) / 2 * h;
}
}tree[MAXN << 1];
int tail = 1, root = 1;
std::queue <int> q;
int newnode() {
if (q.empty()) {
return ++tail;
} else {
int u = q.front(); q.pop(); return u;
}
}
void delnode(int u) {
memset(tree + u, 0, sizeof (tree[u]));
q.push(u);
}
long long tot = 0;
std::vector <int> pts[MAXN];
void update(int p) {
int tmp = root;
std::vector <int> seq;
while (1) {
seq.push_back(tmp);
if (tree[tmp].ls && tree[tmp].ls -> l <= p && p <= tree[tmp].ls -> r) {
tmp = tree[tmp].ls - tree;
} else if (tree[tmp].rs && tree[tmp].rs -> l <= p && p <= tree[tmp].rs -> r) {
tmp = tree[tmp].rs - tree;
} else {
break;
}
}
Node *lp, *rp;
{
int cur = seq[seq.size() - 1];
tot -= tree[cur].cal();
if (tree[cur].l < p) {
int nw = newnode();
tree[nw].ls = tree[cur].ls;
lp = tree + nw;
tree[nw].l = tree[cur].l, tree[nw].r = p - 1;
tree[nw].h = tree[cur].h;
tot += tree[nw].cal();
} else {
lp = 0;
}
if (tree[cur].r > p) {
int nw = newnode();
tree[nw].rs = tree[cur].rs;
rp = tree + nw;
tree[nw].l = p + 1, tree[nw].r = tree[cur].r;
tree[nw].h = tree[cur].h;
tot += tree[nw].cal();
} else {
rp = 0;
}
delnode(cur);
}
for (int i = seq.size() - 2; i >= 0; i--) {
int cur = seq[i];
tot -= tree[cur].cal();
if (tree + seq[i + 1] == tree[cur].ls) {
if (lp) {
tot -= lp -> cal();
lp -> h += tree[cur].h;
tot += lp -> cal();
}
if (tree[cur].r > p) {
tree[cur].l = p + 1;
tree[cur].ls = rp;
tot += tree[cur].cal();
rp = tree + cur;
} else {
rp = 0;
}
} else {
if (tree[cur].l < p) {
tree[cur].r = p - 1;
tree[cur].rs = lp;
tot += tree[cur].cal();
lp = tree + cur;
} else {
lp = 0;
}
if (rp) {
tot -= rp -> cal();
rp -> h += tree[cur].h;
tot += rp -> cal();
}
}
}
root = newnode();
tree[root].l = 1, tree[root].r = c;
tree[root].ls = lp;
tree[root].rs = rp;
}
void main() {
for (int i = 1; i <= n; i++) {
pts[x[i]].push_back(y[i]);
}
long long ans = (long long) c * (c + 1) / 2 * r * (r + 1) / 2;
tree[root].l = 1, tree[root].r = c;
for (int i = 1; i <= r; i++) {
tree[root].h++;
tot += (long long) c * (c + 1) / 2;
for (int j = 0; j < (int) pts[i].size(); j++) {
update(pts[i][j]);
}
ans -= tot;
}
printf("%lld\n", ans);
}
}