P8164 [JOI 2022 Final] 沙堡 2 题解

Description

JOI 君在沙滩上堆沙堡,他已经做好了一个沙堡,沙堡可以使用一个 H×WH\times W 的二维矩形表示,其被划分成若干个 1×11\times 1 的小格子,格子高度互相不同。

JOI 君决定在沙堡上游走,他可以从任意一个点出发,向上下左右四个方向行走,必须满足他行走的路径单调下降。

出于一些原因,JOI 君想知道,在他所有可能的行走路径中,恰好覆盖了一个子矩形的路径数有多少条。

对于全部数据,H,W1H,W\ge 1HW5×104HW\le 5\times 10^41Ai,j1071\le A_{i,j}\le 10^7Ai,jA_{i,j} 互不相同。

Solution

首先一个子矩形合法的条件为子矩形内的每个点 (i,j)(i,j),都满足其值域上的前驱和后继都与其相邻。

但是直接暴力判断最多只做到 O(H3W2)O\left(H^3W^2\right),过不了。

考虑对每个点构造一个权值,使得这些权值的和为一个定值。

XX 为一个极大值,vi,jv_{i,j} 表示 (i,j)(i,j) 的权值,wi,jw_{i,j} 表示 (i,j)(i,j) 四周小于 ai,ja_{i,j} 的最大权值,如果没有则为 00。如果 ai,ja_{i,j} 大于其四周的数,则 vi,j=Xwi,jv_{i,j}=X-w_{i,j},否则 vi,j=ai,jwi,jv_{i,j}=a_{i,j}-w_{i,j}

容易发现一个合法矩形的所有 vi,jv_{i,j} 的和为 XX,证明就考虑如果每个点向周围比它大的最大位置连边,会连出一个内向树森林,而 vi,jv_{i,j} 实际上是把每个内向树划分成若干条链,每条链的权值为 XX,所以总权值为 XX 乘以链数,于是矩形合法的条件即为总权值为 XX

枚举矩形的上下边界和右端点,并在枚举的过程中维护每个位置的权值,用哈希表记录某个前缀的权值即可。

时间复杂度:O(H2W)O(H^2W)

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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>

#define int int64_t

const int kMaxT = 5e4 + 5, kInf = 1e10;
const int kD[][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};

int n, m, t;
int unq[kMaxT], sum[kMaxT], valt1[kMaxT], valt2[kMaxT], valt3[kMaxT];
std::vector<std::vector<int>> a, val1, val2, val3;

void init(std::vector<std::vector<int>> &v, int n = ::n, int m = ::m) {
v.resize(n + 2, std::vector<int>(m + 2));
}

int getid(int x) {
return std::lower_bound(unq + 1, unq + 1 + t, x) - unq;
}

void discrete() {
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
unq[++t] = a[i][j];
std::sort(unq + 1, unq + 1 + t);
t = std::unique(unq + 1, unq + 1 + t) - (unq + 1);
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
a[i][j] = getid(a[i][j]);
}

void work() {
std::swap(n, m);
std::vector<std::vector<int>> b(n + 1, std::vector<int>(m + 1));
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
b[i][j] = a[j][i];
a = b;
}

int getval1(int l, int r, int x, int y) {
int mx = 0, _mx = 0;
for (auto [dx, dy] : kD) {
int tx = x + dx, ty = y + dy;
if (tx >= l && tx <= r && ty >= 1 && ty <= m) {
if (a[tx][ty] > a[x][y]) mx = std::max(mx, a[tx][ty]);
if (a[tx][ty] < a[x][y]) _mx = std::max(_mx, a[tx][ty]);
}
}
if (!mx) mx = kInf;
else mx = a[x][y];
return mx - _mx;
}

int getval2(int l, int r, int x, int y) {
int mx = 0, _mx = 0;
for (auto [dx, dy] : kD) {
int tx = x + dx, ty = y + dy;
if (tx >= l && tx <= r && ty >= 1 && ty <= m && dy != -1) {
if (a[tx][ty] > a[x][y]) mx = std::max(mx, a[tx][ty]);
if (a[tx][ty] < a[x][y]) _mx = std::max(_mx, a[tx][ty]);
}
}
if (!mx) mx = kInf;
else mx = a[x][y];
return mx - _mx;
}

int getval3(int l, int r, int x, int y) {
int mx = 0, _mx = 0;
for (auto [dx, dy] : kD) {
int tx = x + dx, ty = y + dy;
if (tx >= l && tx <= r && ty >= 1 && ty <= m && dy != 1) {
if (a[tx][ty] > a[x][y]) mx = std::max(mx, a[tx][ty]);
if (a[tx][ty] < a[x][y]) _mx = std::max(_mx, a[tx][ty]);
}
}
if (!mx) mx = kInf;
else mx = a[x][y];
return mx - _mx;
}

void dickdreamer() {
std::cin >> n >> m;
init(a);
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
std::cin >> a[i][j];
if (n > m) work();
init(val1), init(val2), init(val3);
discrete();
int64_t ans = 0;
for (int l = 1; l <= n; ++l) {
for (int i = 1; i <= m; ++i) {
valt1[i] = val1[l][i] = getval1(l, l, l, i);
valt2[i] = val2[l][i] = getval2(l, l, l, i);
valt3[i] = val3[l][i] = getval3(l, l, l, i);
}
for (int r = l + 1; r <= n; ++r) {
__gnu_pbds::gp_hash_table<int, int> mp;
int sum = 0;
for (int i = 1; i <= m; ++i) {
val1[r][i] = getval1(l, r, r, i);
valt1[i] += getval1(l, r, r - 1, i) - val1[r - 1][i] + val1[r][i];
val2[r][i] = getval2(l, r, r, i);
valt2[i] += getval2(l, r, r - 1, i) - val2[r - 1][i] + val2[r][i];
val3[r][i] = getval3(l, r, r, i);
valt3[i] += getval3(l, r, r - 1, i) - val3[r - 1][i] + val3[r][i];
if (mp.find(kInf - sum - valt3[i]) != mp.end()) ans += mp[kInf - sum - valt3[i]];
sum += valt1[i];
++mp[valt2[i] - sum];
}
}
}
for (int i = 1; i <= n; ++i) {
int lst[2] = {-1, -1};
std::vector<int> vec;
for (int j = 1; j < m; ++j)
vec.emplace_back(a[i][j] < a[i][j + 1]);
ans += m;
for (int j = 0; j < (int)vec.size(); ++j) {
if (!vec[j]) {
ans += j - lst[1];
lst[0] = j;
} else {
ans += j - lst[0];
lst[1] = j;
}
}
}
for (int i = 1; i <= m; ++i) {
int lst[2] = {-1, -1};
std::vector<int> vec;
for (int j = 1; j < n; ++j)
vec.emplace_back(a[j][i] < a[j + 1][i]);
for (int j = 0; j < (int)vec.size(); ++j) {
if (!vec[j]) {
ans += j - lst[1];
lst[0] = j;
} else {
ans += j - lst[0];
lst[1] = j;
}
}
}
std::cout << ans << '\n';
}

int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
// std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}

P8164 [JOI 2022 Final] 沙堡 2 题解
https://sobaliuziao.github.io/2024/10/24/post/82c29c71.html
作者
Egg_laying_master
发布于
2024年10月24日
许可协议