首页 > 试题广场 >

最大稳定数值

[编程题]最大稳定数值
  • 热度指数:401 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 256M,其他语言512M
  • 算法知识视频讲解
Bingbong有一棵结点总数为 n 且根节点编号为 1 的有根树,第 i 个结点的权值为 a_i

一个结点称为"支撑结点"当且仅当其满足以下所有条件:

上层结点:除自身结点以外的祖先节点。

下层结点:除自身结点以外的子孙节点。

1.该结点的所有上层结点权值之和大于等于当前结点的权值(如果无上层结点,则其权值之和为0)。

2.该结点的所有下层结点权值之和小于等于当前结点的权值(如果无下层结点,则其权值之和为0)。

一棵有根树的稳定值为该有根树"支撑结点"的个数,为了让这棵树的稳定值达到最大,Bingbong得到了一次删除树边的机会(可以不删除),即选择两个结点 u,v ( u 是 v 的父节点),然后把连接 u,v 的边删除,即把以 v 为根的子树删除。请你帮助他计算出该有根树可能达到的最大稳定值。

注:当选择删除子树时,该子树的支撑结点不计入答案。

请回忆:

- 祖先结点:沿树根到某一结点路径上的所有结点都是这个结点的祖先结点;
- 子孙结点:某一结点的子树中的所有结点是这个结点的子孙;

输入描述:
第一行一个整数 n(1\leq n\leq 10^5),表示该有根树的结点个数。

第二行包含 n 个整数,第 i 个数为 a_i(1\leq a_i\leq 10^9),表示第 i 个结点的权值。

第三行包含 n 个整数,第 i 个数为 father_i(1\leq father_i\leq i-1),表示第 i 个结点的父亲结点的编号,特别地 father_1=0


输出描述:
一个整数,表示该有根树可能达到的最大稳定值。
示例1

输入

5
10 2 3 1 2
0 1 1 2 3

输出

4
示例2

输入

6
10 10 3 10 7 40
0 1 1 2 3 4

输出

3

备注:

对于样例一:无需删边。

对于样例二:删除结点4和结点6的边即可。

这道题有点复杂。

首先我们要分析“删除子树”的操作会改变什么?
1. 它能不能改变“祖先节点权值和”?
不能!意味着不满足这条的节点永远不可能变成支撑节点。
2. 它能不能改变“子孙节点权值和”?
它能减少子孙节点的权值和,这条路径上留下的每个节点都会减少这棵子树的权值。
这就意味着不满足这条的节点有可能变成支撑节点,支撑节点数量增加。
3. 它会带来什么损失?
子树里面包含的所有支撑节点都会随之消失,支撑节点数量减少。

有了这个前提,我们就可以按图索骥了:
我们的目标是支撑节点最多,显然我们要DFS每个节点,最大化收益。
收益是什么?因为删除子树而增加的支撑节点数,减去子树里包含的支撑节点数。

后者是个固定值,很好算。主要看前者。
原本不是支撑节点要变成支撑节点,首先它必须在这条路径上
还要它所“差”的权值(require),要比子树的权值和少,否则不够弥补。
它所require的权值,就是子孙节点的权值和减去当前节点权值
现在我们需要:从这条路径所有require>0的节点中,找到小于等于子树权值和的数量

当然你可以用栈来存放,对于每个节点都遍历一遍栈。
但还是太慢,特别如果树蜕化成链表,这个复杂度可能TLE
需要动态添加和删除,并且要很快计算<=指定值的数据结构?可以用树状数组
我们用require值做key,遍历的时候+1,回溯的时候-1,就可以了。
没问题了?不,require值的范围是权值范围×节点数,这样又会MLE
我们发现require范围虽然广,但数量不多(节点数)
所以可以通过保序映射给它离散化到小空间,问题得到解决。

一共要进行两次DFS:
第1次遍历计算每个子树的权值和、包含的支撑节点计数,还有可以转变的节点的require值。
第2次遍历求取删除子树能获得的最大收益。
为什么不能一次遍历?
因为计算删除子树的收益需要所有祖先节点的require值。
而这个require值又依赖子孙节点的权值和,只能在回溯时计算。

代码如下:
public static void main(String[] args) {
    Scanner in = new Scanner(System.in);
    int n = in.nextInt();
    head = new int[n + 1];
    nxt = new int[n + 1];
    to = new int[n + 1];
    a = new int[n + 1]; // 权值
    sum = new long[n + 1]; // 子树的权值和
    count = new int[n + 1]; // 子树的支撑节点计数
    require = new long[n + 1]; // 还差多少权值变成支撑节点
    for (int i = 1; i <= n; i++) a[i] = in.nextInt();
    in.nextInt(); // 根节点没有parent,跳过
    for (int i = 2; i <= n; i++) addEdge(in.nextInt(), i);
    dfs1(1, 0); // 第1次遍历:求取sum、count、require
    FenwickTree tree = new FenwickTree(Arrays.copyOfRange(require, 2, n + 1));
    int max = dfs2(1, tree, 0); // 第2次遍历:求取删除子树的最大收益
    System.out.print((count[1] + max) + "\n");
}
private static void dfs1(int u, long ancestor) {
    for (int i = head[u]; i > 0; i = nxt[i]) {
        int v = to[i];
        dfs1(v, ancestor + a[u]);
        sum[u] += sum[v]; // 子树的权值和
        count[u] += count[v]; // 子树的支撑节点计数
    }
    if (ancestor >= a[u]) { // 祖先节点权值和大于等于当前节点
        if (sum[u] <= a[u]) { // 子孙节点权值和小于等于当前节点
            count[u]++; // 是支撑节点
        } else { // 不是支撑节点,还差多少
            require[u] = sum[u] - a[u];
        }
    }
    sum[u] += a[u]; // 子树的权值和是包含当前节点的
}
private static int dfs2(int u, FenwickTree tree, int max) {
    if (require[u] > 0) tree.add(require[u], 1);
    for (int i = head[u]; i > 0; i = nxt[i]) {
        max = dfs2(to[i], tree, max);
    }
    if (require[u] > 0) tree.add(require[u], -1);
    return Math.max(max, tree.query(sum[u]) - count[u]);
}

private static class FenwickTree { // 带离散映射的树状数组
    private int[] array;
    private long[] mapping;
    private FenwickTree(long[] mapping) {
        Arrays.sort(mapping);
        this.mapping = mapping;
        this.array = new int[mapping.length + 1];
    }
    private int translate(long val) {
        int idx = Arrays.binarySearch(mapping, val);
        return idx < 0 ? -(idx + 2) : idx;
    }
    private void add(long val, int x) {
        int i = translate(val) + 1;
        while (i < array.length) {
            array[i] += x;
            i += Integer.lowestOneBit(i);
        }
    }
    private int query(long val) {
        int i = translate(val) + 1;
        int x = 0;
        while (i > 0) {
            x += array[i];
            i -= Integer.lowestOneBit(i);
        }
        return x;
    }
}

发表于 2026-04-18 21:36:13 回复(0)