1 条题解
-
0
题目分析:
我们需要在有根树上为每个节点分配购买数量,使得每个节点的子树和满足给定的上下界约束,并且总成本最小。关键观察是:
- 树形结构:节点构成有根树,根为节点1
- 子树和约束:对于每个节点 i,其子树和 a_i 必须满足 l_i ≤ a_i ≤ r_i
- 成本最小化:总成本 = Σ(c_i × b_i),需要最小化
关键思路
经过分析,我们发现:
- 这是一个树形动态规划问题
- 需要自底向上处理,从叶子节点开始
- 对于每个节点,需要维护其子树的最小和最大可能和
- 在满足约束的前提下,优先在价格低的节点分配更多数量
算法步骤
- 建树:根据父节点信息构建树结构
- 后序遍历:自底向上处理每个节点
- 可行性检查:对于每个节点,检查子节点的约束是否能满足当前节点的约束
- 贪心分配:在可行范围内,优先在价格低的节点分配
- 输出结果:如果可行则输出最小成本和分配方案,否则输出-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)
关键点总结
- 核心思想:树形DP自底向上计算可行范围
- 可行性检查:每个节点的 min_sum 和 max_sum 必须满足 min_sum ≤ max_sum
- 贪心策略:优先在价格低的节点分配更多数量
- 实现技巧:使用迭代DFS避免递归深度问题,使用链式前向星存储树结构
这个解法能够高效处理最大规模的数据(∑n ≤ 10^5),并且正确解决所有测试用例。
- 1
信息
- ID
- 5274
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 8
- 标签
- 递交数
- 3
- 已通过
- 1
- 上传者