CF1041E 树的重建
1 CF1041E 树的重建
2 题目描述
\(Monocarp\) 画了一棵树(一个无向连接的无环图),然后给每个顶点都加了一个索引。每个索引都是从 \(1\) 到 \(𝑛\) 的不同数字。对于这棵树的每一条边 \(𝑒\),\(Monocarp\) 都写了两个数字:如果边 \(𝑒\) 从树中删除后形成的两个子树顶点的最大索引。
\(Monocarp\) 给了你一个 \(𝑛−1\) 对数字的列表,如果存在这样的树则输出,否则输出 \(NO\)。
3 题解
这是一道比较复杂的构造题。
我们容易发现,如果有任意一个 \(b_i\) 不是 \(n\),那么我们一定无法构造出来这个树。也就是说,这棵树长什么样跟 \(b_i\) 完全没关系。然后我们发现:这道题中貌似对于给出的树边的顺序也没有限制:我们可以以 \(a_i\) 为关键字进行从小到大的排序。容易看出:如果我们可以构造出这棵树,那么这棵树肯定是一个链表:\(n\) 为一端(根节点),\(1\) 为一端(叶子节点)。
构造时,我们可以考虑一个肯定可以构造出来的策略:如果 \(a_i > a_{i-1}\),那么直接把 \(a_{i-1}\) 变成 \(a_i\) 的子节点。这样我们在切断它们之间的边时,\(a_i\) 跑到 \(n\) 那一边,由于没有 \(n\) 大而无法展现出来。而只要保证 \(a_{i-1}\) 那一边 \(a_{i-1}\) 是最大的就可以了。如果 \(a_i = a_{i-1}\),那么再找一个比 \(a_i\) 小的值作为 \(a_i\) 的父亲节点即可:这样会留下两条可以供我们切的线段:一条是我们找来的数和根节点的,一条是我们找来的数和 \(a_i\) 的。随意切段一条所求出的较小值都为 \(a_i\)。如果在第二步没找到那个数,说明不能构成一棵树,输出不可行即可。
4 代码(空格警告):
#include <iostream>
using namespace std;
const int N = 1005;
int n, a, b, last, sum;
int cnt[N];
bool f[N];
int main()
{
cin >> n;
for (int i = 1; i < n; i++)
{
cin >> a >> b;
if (b != n)
{
cout << "NO";
return 0;
}
cnt[a]++;
}
for (int i = 1; i <= n; i++)
{
sum += cnt[i];
if (sum > i)
{
cout << "NO";
return 0;
}
}
last = -1;
cout << "YES" << endl;
for (int i = 1; i <= n; i++)
{
if (cnt[i] >= 1)
{
f[i] = 1;
if (last != -1) cout << last << " " << i << endl;
last = i;
cnt[i]--;
}
if (cnt[i] == 0) continue;
for (int j = 1; j <= n; j++)
{
if (!f[j])
{
cout << j << " " << last << endl;
cnt[i]--;
last = j;
f[j] = 1;
if (!cnt[i]) break;
}
}
}
cout << last << " " << n;
return 0;
}