【题解】兔纸的公司
前言
这道题作为一道树形 \(+\) 贪心的题目而言,还是具有很大的参考价值的。从这道题的解法来看,并 不是 所有的题目都可以使用 确切的算法 解决。这道题使用贪心,是建立在题目中的 重要性质 的基础上。因此,当思路枯竭、题目没有显著特征的时候,不妨推敲一下题目的性质,针对这个性质来设计算法。
题目大意
给定一颗有 \(n\) 个结点的有根树,树的根结点为 \(1\) 。每次可以选择一对父子结点交换,使得父亲的点权 \(+ 1\),儿子的点权 \(- 1\) 。试求出是否可以通过若干次交换,使得所有父亲结点的点权大于等于其子结点的点权,也就是大根堆。
\(t \leq 10, n \leq 10^5\)
解题思路
这道题需要维护一棵树,考虑和树有关的算法,发现并不能很好地维护这棵树。因此,我们尝试来推敲这道题目的性质。
我们可以发现无论如何交换,结点 \(i\) 的点权 \(a_i\) 与其在树中的深度 \(d_i\) 的差都不变。因为当结点 \(i\) 与其子结点交换时,根据题意,深度和点权都会增加 \(1\) ,所以差值不变。
我们令 \(b_i = a_i - dep_i\) 。我们可以猜测:可否通过 \(b_i\) 的大小关系来反推出 \(a_i\) 的大小关系?不妨假设当 \(b_i > b_j\) 时,\(a_i \geq a_j\) ,其中 \(j\) 是 \(i\) 的子结点。下面尝试证明。
因为 \(b_i > b_j\) ,代入可得 \(a_i - dep_i > a_j - dep_j\) ,代入 \(dep_j = dep_i + 1\) 可得 \(a_i - dep_i > a_j - dep_i - 1\) 。等式两边同时加上 \(dep_i\) 可得 \(a_i > a_j - 1\) 。因为题目中给定的 \(a\) 都是整数,所以有 \(a_i \geq a_j\) 。
有了上面的性质,我们可以尝试 转化题意 了。因为无论经过多少次交换,\(b\) 都不会改变。又因为通过 \(b\) 的大小关系,我们可以反推出 \(a\) 的大小关系,也就是说,我们不关心 \(a\) 的值。那么实际上因为结点可以任意交换,题目就可以转化为 将 \(b\) 填入这棵形态固定的树中,使得父结点的 \(b\) 值总是大于子结点的 \(b\) 值。
我们可以开始考虑进一步的做法了。显然,我们必须把最大的 \(b_i\) 填在根结点。因为只有当 \(b_i\) 严格大于 \(b_j\) 的时候才能满足 \(a_i \geq a_j\) ,因此最大的 \(b_i\) 只能有 \(1\) 个。
接下来,我们考虑其他的值是否可以重复。实际上,我们可以把重复的 \(b\) 值填在根结点到不同叶子结点的多条路径上。换句话说,我们可以把根结点从树中删去,形成若干条链。只要重复的 \(b\) 值出现的次数不超过链的总数,我们就可以把 \(b\) 值填在两两不同的链上,从而避免出现 \(b_i \leq b_j\) 的情况。这样,我们还需要统计每个 \(b\) 值出现的次数,记为 \(c_i\)。
我们贪心地想,先处理 \(c_i\) 较大的 \(b_i\) 一定较优。因为先填 \(c_i\) 较小的 \(b_i\) ,可能会把若干条链占满。此时较大的 \(b_i\) 就有可能填不下去。反之,如果 \(c_i\) 较小,那么即使链数变小,能填下去的可能性还是比 \(c_i\) 较大的 \(b\) 值要大。再次贪心,我们先填空位较多的链,一定比先填空位较少的链更优。因为我们先填了 \(c_i\) 较大的 \(b\) 值,如果先填在了空位较少的链上,就有可能导致链数变少,可能对后面的 \(b\) 值造成影响。贪心的过程可以用排序和优先队列来实现。
参考代码
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;
const int maxn = 1e5 + 5;
int t, n;
int cnt, tot;
int fa[maxn], dep[maxn];
int c[maxn], val[maxn];
bool flag, vis[maxn];
long long a[maxn], b[maxn];
bool cmp(int a, int b) {
return a > b;
}
void get_dep(int u) {
if (!dep[fa[u]]) {
get_dep(fa[u]);
}
dep[u] = dep[fa[u]] + 1;
}
void init() {
memset(a, 0, sizeof(a));
memset(b, 0, sizeof(b));
memset(c, 0, sizeof(c));
memset(dep, 0, sizeof(dep));
memset(val, 0, sizeof(val));
memset(vis, false, sizeof(vis));
cnt = tot = 0;
flag = true;
}
void read() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
}
for (int i = 1; i <= n; i++) {
scanf("%d", &fa[i]);
vis[fa[i]] = true;
}
}
void work() {
dep[1] = 1;
b[1] = a[1] - 1;
for (int i = 2; i <= n; i++) {
if (!dep[i]) {
get_dep(i);
}
b[i] = a[i] - dep[i];
}
sort(b + 1, b + n + 1);
if (b[n] == b[n - 1]) {
printf("So Sad!\n");
flag = false;
return;
}
for (int i = 1; i <= n - 1; i++) {
if (b[i] != b[i - 1]) {
tot++;
}
c[tot]++;
}
sort(c + 1, c + tot + 1, cmp);
}
void solve() {
int f;
if (!flag) {
return;
}
priority_queue<int> pq;
for (int i = 1; i <= n; i++) {
if (!vis[i]) {
cnt++;
pq.push(dep[i] - 1);
}
}
for (int i = 1; i <= tot; i++) {
// printf("%d ", i);
if (cnt < c[i]) {
printf("So Sad!\n");
flag = false;
break;
}
for (int j = 1; j <= c[i]; j++) {
f = pq.top();
pq.pop();
val[j] = f - 1;
}
for (int j = 1; j <= c[i]; j++) {
if (val[j]) {
pq.push(val[j]);
} else {
cnt--;
}
}
}
if (flag) {
printf("nice Bunny!\n");
}
}
int main() {
scanf("%d", &t);
while (t--) {
init();
read();
work();
solve();
}
return 0;
}