[2018 GCPC M] Mountaineers
https://vjudge.net/problem/Gym-102021M
题意:
给你一个最大为\(500*500\)的矩阵,你可以在矩阵中的点中上下左右移动。接下来最多\(10^5\)次询问,每次询问两个点,你要从第一个点走到第二个点,使得路程中经过的点权值的最大值最小,问你这个点权值是多少。
思路:
朴素的想法是二分权值,进行\(dfs\),但是时间复杂度是\(O(n^2 * q * log10^6)\)的。后来尝试加记忆化搜索,但是失败了。
既然无法在每次询问时将时间复杂度降下来,那么需要考虑能否将询问一起处理。这道题可以用\(LCA\)的方法解,大概的方法是优先处理点权较小的点,从四周点权不大于它的点向它建一条有向边,形成一棵树,这样就能保证一路上经过的点的权值最大值最小,并且拥有最大权值的点是它们的\(LCA\)。然后以权值最大的点为根,每次询问输出两个点的\(LCA\)的权值就行了。
这里介绍一种\(dsu\)的方法。我们将所有询问的编号记录在对应的点上。然后优先处理点权较小的点,每次将四周点权不大于它的点与它合并在并查集里,合并的时候看下它们是否有相同的询问编号,如果有,那么这次询问的答案就是当前点的权值。注意在合并的时候,我们要判断两个点拥有的询问数量,将小的合并在大的里面。因为我们在看两个点是否有相同的询问编号时,是需要进行遍历操作的,最坏的情况下时间复杂度还是会达到\(O(n^2 * q)\)。但如果我们每次都遍历拥有询问数量较小的点,遍历的总次数就会是\(O(q)\)级别的,所以不加上查询是否有相同的编号时,总时间复杂度降到了\(O(n^2 + q)\)。为了让各种操作都能方便进行,我们选择\(set\)来储存询问的信息,时间复杂度大概为\(O(n^2 + qlogq)\),但是常数比较大。
#include <bits/stdc++.h>
using namespace std;
#define endl "\n"
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int Q = 1e5 + 7;
const int N = 500 * 500 + 7;
set<int> sz[N];
int n, m, q, w[N], fa[N], res[Q];
int mov[][2] = {0, 1, 0, -1, 1, 0, -1, 0};
bool check(int x, int y) {
if (x < 1 ||x > n || y < 1 || y > m) return false;
return true;
}
int ID(int x, int y) {
return (x - 1) * m + y;
}
int find(int x) {
return x == fa[x]? x : fa[x] = find(fa[x]);
}
void merge(int x, int y, int now) {
x = find(x), y = find(y);
if (x == y) return;
if (sz[x].size() > sz[y].size()) swap(x, y);
for (auto i : sz[x]) {
if (sz[y].count(i)) {
res[i] = now;
sz[y].erase(i); // 删除操作,降低空间复杂度的同时也降低了时间复杂度
} else {
sz[y].insert(i);
}
}
sz[x].clear();
fa[x] = y;
}
void solve() {
cin >> n >> m >> q;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
cin >> w[ID(i, j)];
fa[ID(i, j)] = ID(i, j);
}
}
for (int i = 1, x1, x2, y1, y2; i <= q; ++i) {
cin >> x1 >> y1 >> x2 >> y2;
if (x1 == x2 && y1 == y2) {
res[i] = w[ID(x1, y1)];
continue;
}
sz[ID(x1, y1)].insert(i);
sz[ID(x2, y2)].insert(i);
}
vector< tuple<int, int, int> > v;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
v.emplace_back(make_tuple(w[ID(i, j)], i, j));
}
}
sort(v.begin(), v.end());
for (auto t : v) {
int ww, xx, yy;
tie(ww, xx, yy) = t;
for (int i = 0; i < 4; ++i) {
int fx = xx + mov[i][0], fy = yy + mov[i][1];
if (check(fx, fy)) {
if (w[ID(fx, fy)] > w[ID(xx, yy)]) continue;
merge(ID(xx, yy), ID(fx, fy), ww);
}
}
}
for (int i = 1; i <= q; ++i) {
cout << res[i] << endl;
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);cout.tie(nullptr);
cout << fixed << setprecision(20);
int _T = 1;
while (_T--) solve();
return 0;
}
/*
3 5 3
1 3 2 1 3
2 4 5 4 4
2 1 3 2 2
1 1 3 2
2 4 2 2
1 4 3 4
*/