【2022 省选训练赛 Contest 15 C】老园丁与小司机(左偏树)(DP)

老园丁与小司机

题目链接:2022 省选训练赛 Contest 15 C

题目大意

给你一棵树,然后有一些路径(满足路径的两端的点的 LCA 是其中之一),每个路径有选的费用。
然后要你花费最小的费用使得每个边都在选的至少一条路径中,或输出无解。

思路

考虑到路径的特别,我们可以有这么一种 DP。
fi,j 为解决了 i 的子树,并且也解决了一条往上到 j 的链的最小费用。
那每次合并两个子树就找到它们里面各自最小的,给对面加上,然后合并两个子树的值。
然后跟所有的儿子合并完之后,我们要把不合法的踢出(就是 j 的深度小于等于 i,那到处理上面的点的时候它就不合法了)
(当然是 1 就不用踢了)

然后考虑能不能优化这个过程。
看到合并,求最小的,踢出不合法(而且你会发现每次就是不断踢深度最小就可以了)
那我们可以用可并堆【按深度排,然后维护子树最小值,子树加值用懒标记】,用左偏树来实现即可。

代码

#include<cstdio> #include<vector> #include<iostream> #include<algorithm> #define ll long long using namespace std; const int N = 300000 + 100; int n, m, rt[N], tot, deg[N]; vector <int> G[N]; vector <pair<int, int> > L[N]; struct ZP_Tree { ll val[N], lzy[N], minn[N]; int ls[N], rs[N], h[N], deg[N]; void up(int now) { minn[now] = val[now]; if (ls[now]) minn[now] = min(minn[now], minn[ls[now]]); if (rs[now]) minn[now] = min(minn[now], minn[rs[now]]); } void downa(int x, ll k) { val[x] += k; minn[x] += k; lzy[x] += k; } void down(int now) { if (lzy[now]) { if (ls[now]) downa(ls[now], lzy[now]); if (rs[now]) downa(rs[now], lzy[now]); lzy[now] = 0; } } int merge(int x, int y) { if (!x || !y) return x + y; if (deg[x] < deg[y]) swap(x, y); down(x); rs[x] = merge(rs[x], y); if (h[rs[x]] >= h[ls[x]]) swap(rs[x], ls[x]); h[x] = h[rs[x]] + 1; up(x); return x; } int delete_(int x) { down(x); return merge(ls[x], rs[x]); } }T; void dfs(int now, int father) { deg[now] = deg[father] + 1; for (int i = 0; i < G[now].size(); i++) { int x = G[now][i]; if (x == father) continue; dfs(x, now); if (!rt[x]) { printf("-1"); exit(0); } if (!rt[now]) { rt[now] = rt[x]; continue; } ll vx = T.minn[rt[x]], vy = T.minn[rt[now]]; T.downa(rt[now], vx); T.downa(rt[x], vy); rt[now] = T.merge(rt[now], rt[x]); } ll va = T.minn[rt[now]]; for (int i = 0; i < L[now].size(); i++) { int v = L[now][i].first, w = L[now][i].second; ++tot; T.deg[tot] = deg[v]; T.val[tot] = T.minn[tot] = va + w; rt[now] = T.merge(rt[now], tot); } if (now != 1) { while (rt[now] && T.deg[rt[now]] >= deg[now]) { rt[now] = T.delete_(rt[now]); } } } int main() { scanf("%d %d", &n, &m); for (int i = 1; i < n; i++) { int x, y; scanf("%d %d", &x, &y); G[y].push_back(x); G[x].push_back(y); } for (int i = 1; i <= m; i++) { int u, v, c; scanf("%d %d %d", &u, &v, &c); L[u].push_back(make_pair(v, c)); } dfs(1, 0); printf("%lld", T.minn[rt[1]]); return 0; }

__EOF__

本文作者あおいSakura
本文链接https://www.cnblogs.com/Sakura-TJH/p/16053991.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   あおいSakura  阅读(24)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示