题目大意
给出一棵带权树,其中所有父节点的权值一定严格大于子节点。给出了所有叶子节点的权值以及两两叶子之间的 最近公共祖先 L C A LCA LCA 的权值,试着还原这棵树。
解题思路
树的构造问题大都从特殊到一般、从叶子结点开始构造。
对于两个叶子结点,显然第一次操作时只需要新建一个节点,然后将他们连接起来;对于两棵分别连好了两个叶子节点的子树,如下图所示,若 1 4 1~~4 1 4 的 L C A LCA LCA 的权值大于了两个父节点 5 , 6 5,6 5,6,那么我们新建一个 7 7 7 节点连接上这两棵子树。
这个时候似乎已经可以窥见构造规律了,对于每对给出的 L C A LCA LCA信息,从小到大构造。上述子树还会有两种情况,也就是当前的 L C A LCA LCA等于两棵子树的某个根节点,那么将另一棵子树连上去即可。至于如何维护一棵子树的根节点,使用并查集,但是并查集只是维护两个集合的,怎么保证集合的祖先就是根节点?实际上只需要设置当前集合祖先时更新为根节点,这样在路径压缩的时候祖先节点的信息不会改变,只会将下面子节点的祖先节点更新,真是妙用!
易错点: 对于一个节点的子树下若有很多节点,如何保证不生成两个相同的父节点(对于本题的贪心思路来说,尽可能使得相同的 L C A LCA LCA 连接到同一根节点下),那么对于相同的 L C A LCA LCA 权值,排序时先根据序号较小的节点升序然后根据序号较大的节点升序。例如 1 2 3 4 1~~2~~3~~4 1 2 3 4的 L C A LCA LCA都相同,显然 1 2 1~~2 1 2、 1 3 1~~3 1 3、 1 4 1~~4 1 4…这样处理就不会出现上述问题。
reference:https://blog.csdn.net/liufengwei1/article/details/114298063
#include <bits/stdc++.h>
using namespace std;
#define ENDL "\n"
typedef long long ll;
const int maxn = 5e5 + 10;
struct node {
int u, v, w;
bool operator<(const node &p) const {
if (w == p.w) {
if (u == p.u) return v < p.v;
return u < p.u;
}
return w < p.w;
}
};
vector<node> res;
int f[maxn], father[maxn], val[maxn];
int Find(int x) {
return f[x] == x ? x : f[x] = Find(f[x]); }
int main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n;
cin >> n;
for (int i = 1; i <= n * n; i++) f[i] = i;
for (int i = 1, x; i <= n; i++) {
for (int j = 1; j <= n; j++) {
cin >> x;
if (j > i)
res.push_back({
i, j, x});
else if (i == j)
val[i] = x;
}
}
sort(res.begin(), res.end());
int idx = n + 1;
for (auto p : res) {
int fu = Find(p.u), fv = Find(p.v);
if (fu == fv) continue;
int Max = max(val[fu], val[fv]);
if (Max == p.w) {
if (p.w == val[fu])
father[fv] = f[fv] = fu;
else
father[fu] = f[fu] = fv;
} else {
father[fu] = father[fv] = idx;
val[idx] = p.w;
f[fu] = f[fv] = idx;
idx++;
}
}
cout << idx - 1 << ENDL << val[1];
for (int i = 2; i <= idx - 1; i++) cout << ' ' << val[i];
cout << ENDL;
cout << idx - 1 << ENDL;
for (int i = 1; i <= idx - 2; i++) {
if (father[i]) cout << i << " " << father[i] << ENDL;
}
return 0;
}