Loading

【题解】兔纸的公司

前言

这道题作为一道树形 \(+\) 贪心的题目而言,还是具有很大的参考价值的。从这道题的解法来看,并 不是 所有的题目都可以使用 确切的算法 解决。这道题使用贪心,是建立在题目中的 重要性质 的基础上。因此,当思路枯竭、题目没有显著特征的时候,不妨推敲一下题目的性质,针对这个性质来设计算法。

题目大意

给定一颗有 \(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;
}
posted @ 2021-07-24 23:38  kymru  阅读(126)  评论(0编辑  收藏  举报