1 条题解
-
0
C. 最大树值 题解
一、题意理解
给定一棵 个顶点的树,每条边 有两个权值 和 。给每个顶点赋一个 到 的排列值 。
对于边 (),其贡献为:
$$\begin{cases} x, & \text{若 } p_u > p_v, \\[4pt] y, & \text{否则}. \end{cases} $$目标是最大化所有边的贡献之和。
二、核心观察
命题:对于每条边 ,我们可以使其贡献等于 。
证明:
为每条边赋予一个方向:
- 若 ,则期望 (使贡献为 ),定向为 。
- 若 ,则期望 (使贡献为 ),定向为 。
这样,每条边都被赋予了一个方向。
三、DAG 的构造
断言:按上述规则定向后的图是一个有向无环图(DAG)。
证明:
由于原图是一棵树(无环),给每条边定向后不会产生新的环。假设存在一个有向环 ,则根据定向规则,必须有:
这产生了矛盾 。因此定向后的图无环。
四、拓扑排序构造排列
既然定向后的图是 DAG,我们可以对其进行拓扑排序。
设拓扑排序得到的顺序为 。
令排列 满足:
即:拓扑序中越靠前的顶点,赋值越小。
正确性验证:
对于定向边 , 在拓扑序中出现在 之前,因此 ,这正是定向时所期望的大小关系。
因此,每条边的贡献都取到了 ,总和达到理论最大值。
五、算法流程
- 读入树的所有边 。
- 对于每条边:
- 若 ,定向为 (期望 )。
- 若 ,定向为 (期望 )。
- 构建有向图,计算每个顶点的入度。
- 将所有入度为 的顶点加入队列。
- 进行拓扑排序:依次取出队首顶点 ,为其赋值 ,然后将 的所有出边指向的顶点入度减 ,若入度变为 则入队。
- 输出排列 。
六、标程解析
void solve() { int n; std::cin >> n; std::vector<int> deg(n, 0); std::vector<std::vector<int>> g(n); std::vector<std::vector<int>> gg(n); std::vector<Edge> e(n - 1); for (auto &[u, v, x, y] : e) { std::cin >> u >> v >> x >> y; u--, v--; if (x > y) { g[u].push_back(v); // u -> v 期望 p_u > p_v gg[v].push_back(u); // 反向边,用于拓扑排序 deg[u]++; // u 是"大端",入度增加 } else { g[v].push_back(u); // v -> u 期望 p_v > p_u gg[u].push_back(v); deg[v]++; } } std::queue<int> q; for (int i = 0; i < n; i++) { if (deg[i] == 0) { q.push(i); } } std::vector<int> p(n); for (int i = 1; i <= n; i++) { int u = q.front(); p[u] = i; // 拓扑序中第 i 个被赋值的顶点 q.pop(); for (const auto &v : gg[u]) { deg[v]--; if (deg[v] == 0) { q.push(v); } } } for (int x : p) { std::cout << x << ' '; } }步骤详解:
- 读入 和 条边。
- 根据 和 的大小关系定向:
- 若 :期望 ,即 应该比 大。在拓扑排序中, 应后出现。因此添加边 ,并增加 的入度(因为 是依赖方,需要等 先赋值)。
- 若 :期望 ,类似地添加边 。
- 将所有入度为 的顶点入队(它们可以最先被赋值)。
- 进行 BFS 拓扑排序,按出队顺序依次赋值 。
- 输出排列。
注意:标程中的
deg和边方向的定义与常见拓扑排序略有不同:g[u]存储从 出发的边( 应更大)。gg[u]存储指向 的边( 依赖的顶点)。deg[u]表示有多少顶点依赖 (即 必须先被赋值)。
七、代码实现(C++)
#include <bits/stdc++.h> using namespace std; struct Edge { int u, v, x, y; }; void solve() { int n; cin >> n; vector<int> deg(n, 0); vector<vector<int>> g(n); // g[u]: u 应该更大(后赋值) vector<vector<int>> rg(n); // rg[u]: 依赖于 u 的顶点 for (int i = 0; i < n - 1; i++) { int u, v, x, y; cin >> u >> v >> x >> y; u--, v--; if (x > y) { // 期望 p_u > p_v,即 u 应比 v 大 -> u 后赋值 g[u].push_back(v); rg[v].push_back(u); deg[u]++; } else { // 期望 p_v > p_u 或 p_u < p_v,即 v 应比 u 大 -> v 后赋值 g[v].push_back(u); rg[u].push_back(v); deg[v]++; } } queue<int> q; for (int i = 0; i < n; i++) { if (deg[i] == 0) q.push(i); } vector<int> p(n); int cur = 1; while (!q.empty()) { int u = q.front(); q.pop(); p[u] = cur++; for (int v : rg[u]) { deg[v]--; if (deg[v] == 0) q.push(v); } } for (int i = 0; i < n; i++) { cout << p[i] << " \n"[i == n - 1]; } } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int t; cin >> t; while (t--) solve(); return 0; }
八、样例验证
样例 1
3 1 2 2 1 2 3 3 2- 边 :,期望 。定向 ( 应更大,后赋值),。
- 边 :,期望 。定向 ,。
入度:。
拓扑排序:初始 (入度为 )。
- 取出 :。
- ,入队 。
- 取出 :。
- ,入队 。
- 取出 :。
排列 ,最大值 ✅
九、复杂度分析
- 构建有向图:。
- 拓扑排序:。
- 每个测试用例总复杂度 。
- ,完全可行。
- 空间复杂度 。
- 1
信息
- ID
- 6690
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 2
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者