1 条题解
-
0
自动搬运
来自洛谷,原作者为

Register_int
分道扬镳搬运于
2025-08-24 23:03:42,当前版本为作者最后更新于2024-09-16 23:52:13,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
鲜花:这题 idea 来自我真名的拼音首字母缩写。
首先考虑一个 的 dp。先枚举 ,然后设 为 子树内的答案。对于 的儿子 ,若定为重儿子则造成 的贡献,否则造成 的贡献。最优策略就是贪心地将 的前 大设为重儿子,可以得到转移:
$$dp_u=\max((i+1)_{\text{th}}(\{dp_v+w_{u,v}\mid v\in\operatorname{son}(u)\}),\max_{v\in\operatorname{son}(u)}dp_v) $$其中 表示集合 内的第 大。单次可用
nth_element函数做到 。可以发现一个事情:当 时,我们可以把它的所有儿子都设为重儿子。所以这些 本质上是没用的,只要保留所有 的节点后建虚树,节点总数是 的。
但是转移复杂度还是错的啊?你先别急,我们每个节点开一个平衡树/可删对顶堆,维护其下所有 的值即可。新建虚树时暴力删除所有删掉的节点,进行 dp 时暴力加上所有有用的节点,修改次数是 的。
但是虚树是 10 级算法我不会啊?你先别急,我们可以在 的虚树上面建 的虚树,每次搜第一遍找出哪些点要用,再搜第二遍连父亲即可,时间复杂度是所有虚树的大小总和,也是 的。总复杂度带个
priority_queue或者multiset的 log。代码异常简单。
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int MAXN = 2e5 + 10; struct heap { multiset<ll, greater<ll>> s1, s2; void insert(ll x) { if (s2.empty() || x >= *s2.begin()) s1.insert(x); else s2.insert(x); } void erase(ll x) { if (s1.find(x) != s1.end()) s1.erase(s1.find(x)); else if (s2.find(x) != s2.end()) s2.erase(s2.find(x)); } ll kth(int rk) { if (rk > s1.size() + s2.size()) return 0; for (; s1.size() < rk; s1.insert(*s2.begin()), s2.erase(s2.begin())); for (; s1.size() > rk; s2.insert(*--s1.end()), s1.erase(--s1.end())); return s2.empty() ? 0 : *s2.begin(); } } s[MAXN]; struct node { int v, w; node(int v = 0, int w = 0) : v(v), w(w) {} }; vector<node> g[MAXN], tg[MAXN]; int k, d[MAXN]; ll dp[MAXN]; void dfs(int u, int f) { ll res = 0; for (node p : g[u]) { if (p.v == f) continue; dfs(p.v, u); res = max(res, dp[p.v]), s[u].insert(dp[p.v] + p.w); } dp[u] = max(res, s[u].kth(k)); } int vis[MAXN], sz[MAXN]; void init(int u, int f) { int cnt = 0; sz[u] = vis[u] = (!f || d[u] > k); for (node p : g[u]) { if (p.v == f) continue; init(p.v, u); if (sz[p.v]) cnt++; sz[u] += sz[p.v]; } if (!vis[u] && cnt > 1) vis[u] = 1, sz[u]++; } void build(int u, int f, int t, int w) { for (node p : g[u]) if (p.v != f) s[u].erase(dp[p.v] + p.w); for (node p : g[u]) if (p.v != f && !sz[p.v]) s[u].insert(p.w); if (vis[u]) { if (t) tg[t].emplace_back(u, w); t = u; } else for (node &p : g[u]) p.w = w; for (node p : g[u]) if (p.v != f && sz[p.v]) build(p.v, u, t, p.w); g[u] = tg[u], tg[u].clear(); } int n; int main() { scanf("%d", &n); for (int i = 1, u, v, w; i < n; i++) { scanf("%d%d%d", &u, &v, &w); g[u].emplace_back(v, w), d[u]++; g[v].emplace_back(u, w), d[v]++; } for (int i = 2; i <= n; i++) d[i]--; dfs(1, 0), printf("%lld ", dp[1]); for (k = 1; k < n; k++) { init(1, 0), build(1, 0, 0, 0), dfs(1, 0); printf("%lld ", dp[1]); } }审核说还存在一个长剖做法,但是我不会长剖,有会的同学可以写一下谢谢喵。
赛时被骂说这题跟 TECHNOPOLIS 2085 太卡常了,一看写的不是直接搜而是 10 级算法,令人忍俊不禁。
- 1
信息
- ID
- 10521
- 时间
- 1000ms
- 内存
- 512MiB
- 难度
- 6
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者