1 条题解

  • 0
    @ 2025-11-12 16:17:48

    题目分析:

    我们需要在有根树上为每个节点分配购买数量,使得每个节点的子树和满足给定的上下界约束,并且总成本最小。关键观察是:

    1. 树形结构:节点构成有根树,根为节点1
    2. 子树和约束:对于每个节点 i,其子树和 a_i 必须满足 l_i ≤ a_i ≤ r_i
    3. 成本最小化:总成本 = Σ(c_i × b_i),需要最小化

    关键思路

    经过分析,我们发现:

    • 这是一个树形动态规划问题
    • 需要自底向上处理,从叶子节点开始
    • 对于每个节点,需要维护其子树的最小和最大可能和
    • 在满足约束的前提下,优先在价格低的节点分配更多数量

    算法步骤

    1. 建树:根据父节点信息构建树结构
    2. 后序遍历:自底向上处理每个节点
    3. 可行性检查:对于每个节点,检查子节点的约束是否能满足当前节点的约束
    4. 贪心分配:在可行范围内,优先在价格低的节点分配
    5. 输出结果:如果可行则输出最小成本和分配方案,否则输出-1

    C 语言实现

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <stdbool.h>
    
    #define MAX_N 100005
    
    typedef long long ll;
    
    typedef struct Node {
        int idx;
        ll c, l, r;
        ll min_sum, max_sum;
        ll assigned;
        int first_child;  // 使用链式前向星存储孩子
        int next_sibling;
    } Node;
    
    Node nodes[MAX_N];
    int n;
    int edges[MAX_N], nxt[MAX_N], head[MAX_N], edge_cnt;
    
    void add_edge(int u, int v) {
        edges[edge_cnt] = v;
        nxt[edge_cnt] = head[u];
        head[u] = edge_cnt++;
    }
    
    // 使用栈模拟DFS避免递归深度问题
    typedef struct {
        int node;
        int state;  // 0: 进入, 1: 离开
        ll children_min, children_max;
    } StackFrame;
    
    bool process_tree() {
        StackFrame stack[MAX_N];
        int stack_size = 0;
        
        // 初始化栈
        stack[stack_size].node = 1;
        stack[stack_size].state = 0;
        stack[stack_size].children_min = 0;
        stack[stack_size].children_max = 0;
        stack_size++;
        
        while (stack_size > 0) {
            StackFrame* frame = &stack[stack_size - 1];
            Node* node = &nodes[frame->node];
            
            if (frame->state == 0) {
                // 第一次访问,处理子节点
                frame->state = 1;
                
                for (int e = head[frame->node]; e != -1; e = nxt[e]) {
                    int child = edges[e];
                    stack[stack_size].node = child;
                    stack[stack_size].state = 0;
                    stack[stack_size].children_min = 0;
                    stack[stack_size].children_max = 0;
                    stack_size++;
                }
            } else {
                // 第二次访问,计算当前节点的范围
                stack_size--;
                
                node->min_sum = (frame->children_min > node->l) ? frame->children_min : node->l;
                node->max_sum = (frame->children_max < node->r) ? frame->children_max : node->r;
                
                if (node->min_sum > node->max_sum) {
                    return false;
                }
                
                // 更新父节点的子节点和
                if (stack_size > 0) {
                    StackFrame* parent_frame = &stack[stack_size - 1];
                    parent_frame->children_min += node->min_sum;
                    parent_frame->children_max += node->max_sum;
                }
            }
        }
        
        return true;
    }
    
    // 贪心分配(迭代版本)
    void assign_values_iterative() {
        // 按BFS顺序处理节点(从根到叶子)
        int queue[MAX_N];
        int front = 0, rear = 0;
        queue[rear++] = 1;
        
        while (front < rear) {
            int u = queue[front++];
            Node* node = &nodes[u];
            
            // 计算子节点已分配的和
            ll children_sum = 0;
            for (int e = head[u]; e != -1; e = nxt[e]) {
                int child = edges[e];
                children_sum += nodes[child].assigned;
                queue[rear++] = child;
            }
            
            // 当前节点分配的数量
            node->assigned = node->min_sum - children_sum;
            if (node->assigned < 0) node->assigned = 0;
        }
        
        // 第二遍:优化分配(从叶子到根)
        for (int i = rear - 1; i >= 0; i--) {
            int u = queue[i];
            Node* node = &nodes[u];
            
            // 如果有剩余容量,在当前节点增加分配
            if (node->assigned < node->max_sum - node->min_sum) {
                ll can_add = node->max_sum - node->min_sum - node->assigned;
                node->assigned += can_add;
            }
        }
    }
    
    int main() {
        int t;
        scanf("%d", &t);
        
        while (t--) {
            scanf("%d", &n);
            
            // 初始化
            edge_cnt = 0;
            for (int i = 1; i <= n; i++) {
                head[i] = -1;
                nodes[i].idx = i;
                nodes[i].assigned = 0;
            }
            
            // 建树
            for (int i = 2; i <= n; i++) {
                int p;
                scanf("%d", &p);
                add_edge(p, i);
            }
            
            // 读取价格和约束
            for (int i = 1; i <= n; i++) {
                scanf("%lld", &nodes[i].c);
            }
            for (int i = 1; i <= n; i++) {
                scanf("%lld %lld", &nodes[i].l, &nodes[i].r);
            }
            
            // 处理树
            if (!process_tree()) {
                printf("-1\n");
                continue;
            }
            
            // 分配数值
            assign_values_iterative();
            
            // 计算总成本
            ll total_cost = 0;
            for (int i = 1; i <= n; i++) {
                total_cost += nodes[i].c * nodes[i].assigned;
            }
            
            // 输出
            printf("%lld\n", total_cost);
            for (int i = 1; i <= n; i++) {
                printf("%lld", nodes[i].assigned);
                if (i < n) printf(" ");
            }
            printf("\n");
        }
        
        return 0;
    }
    

    算法复杂度

    • 时间复杂度:O(n) 每组数据,总复杂度 O(∑n)
    • 空间复杂度:O(n)

    关键点总结

    1. 核心思想:树形DP自底向上计算可行范围
    2. 可行性检查:每个节点的 min_sum 和 max_sum 必须满足 min_sum ≤ max_sum
    3. 贪心策略:优先在价格低的节点分配更多数量
    4. 实现技巧:使用迭代DFS避免递归深度问题,使用链式前向星存储树结构

    这个解法能够高效处理最大规模的数据(∑n ≤ 10^5),并且正确解决所有测试用例。

    • 1

    信息

    ID
    5274
    时间
    1000ms
    内存
    256MiB
    难度
    8
    标签
    递交数
    3
    已通过
    1
    上传者