Total Eclipse
http://acm.hdu.edu.cn/showproblem.php?pid=6763
题意
这题的题意非常讨厌,我喜欢转化成我的说法:
有一个图模型的城市,建筑之间可能有双向边(可能重边),每个建筑有高度。
每过一个单位时间,海水淹没一个高度,建筑被完全淹没就失去边。
让你对“每个时刻的建筑群(连通块)个数”求和,直到所有建筑被淹没。
思路
这题就需要细心地考虑一个并查集的过程。(只要读对了题意,我们队可以提供三种不同的解法,气死👿)
-
观察到整个过程反过来不影响答案,所以我从反向考虑这个问题。
-
随着淹没高度增加,显然会把高的建筑孤立,甚至有可能最高的建筑要被单独计算多次。
-
从最高的建筑开始:
- 如果当前的建筑相邻建筑有比他高的建筑群(连通块),并且当前这个建筑还不属于这个建筑群。
- 那么把当前建筑划入建筑群,并且,这个建筑群的贡献就是建筑群的高度与当前建筑的差值。
- 要注意,这个建筑划入这个建筑群之后,整个建筑群的有效高度,要变成新来建筑的有效高度。
-
最终格局就等价于对原始图做并查集,并且每个建筑群的有效高度都是这个块的最小高度。
-
把最后每个连通块的有效高度都加入到答案即可。
代码
代码里有一些细节:
按照上面的思路其实是要写带权并查集的,因为你要维护并查集的有效高度。
但其实只要你巧妙的使用合并操作,就可以实现同样的效果。
复杂度是并查集的复杂度+vector建图的复杂度。运行时间慢于第一页的人,可能是我用cin
的缘故。
int fa[MAXN];
int getf(int x) {
if (x == fa[x]) return x;
else return fa[x] = getf(fa[x]);
}
// 注意,我是把右边的祖先合并给左边的祖先。
bool unif(int x, int y) {
int fx = getf(x);
int fy = getf(y);
if (fx != fy) {
fa[fy] = fx;
return true;
}
return false;
}
vector<int> g[MAXN];
int val[MAXN];
int arr[MAXN];
//int vis[MAXN];
void solve(int kaseId = -1) {
int n, m;
ll ans = 0;
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
cin >> val[i];
fa[i] = i;
g[i].clear();
arr[i] = i;
}
for (int i = 1, u, v; i <= m; ++i) {
cin >> u >> v;
g[u].emplace_back(v);
g[v].emplace_back(u);
}
sort(arr + 1, arr + 1 + n, [&](int x, int y) {
return val[x] > val[y];
});
for (int i = 1; i <= n; ++i) {
int u = arr[i];
for (auto v : g[u]) {
if (val[v] >= val[u]) {
if (unif(v, u)) {
v = getf(v);
ans += val[v] - val[u];
val[v] = val[u];
}
}
}
}
for (int i = 1; i <= n; ++i) {
int u = getf(i);
ans += val[u];
val[u] = 0;
}
cout << ans << endl;
}