【ybt金牌导航5-3-2】【luogu P2495】消耗战

消耗战

题目链接:ybt金牌导航5-3-2 / luogu P2495

题目大意

有一个树,每个边有切断的费用,每次选一些点(不会选 1 点),问你最少要用多少费用切边,使得所有选点的点都不能与 1 点连通。

思路

首先容易想到如果一次询问的话,它就是一个树形 DP。
大概就是记录每个点被割最少要多少费用(就是 1 点到它之间的边之间边权最小的那个),然后从叶节点从下而上 DP,可以割自己,也可以把子树的都处理掉。
但是如果自己一定要割就没有第二种,因为你割了自己又割儿子肯定不优。

但是它是多组询问,那你就建个虚树搞搞就好了。
(询问虚树上的距离不要用 LCA 来搞,我一开始是这么搞的,然后卡了三页评测的常也只有 90

代码

#include<cstdio> #include<cstring> #include<algorithm> #define ll long long #define INF 0x3f3f3f3f3f3f3f3f using namespace std; struct node { ll x; int to, nxt; }e[500001], e_[250001]; struct kong { int fa; }t[250001]; int n, x, y, z, le[250001], KK, lca, tmpp; int fa[250001][18], dfn[250001], m, nn; int sta[250001], deg[250001]; int le_[250001], KK_, sp[250001]; ll f[250001], minv[250001], re; bool real[500001]; inline int read() { re = 0; char c = getchar(); while (c < '0' || c > '9') c = getchar(); while (c >= '0' && c <= '9') { re = (re << 3) + (re << 1) + c - '0'; c = getchar(); } return re; } inline void write(ll x) { if (x > 9) write(x / 10); putchar(x % 10 + '0'); } inline ll Min(ll x, ll y) { return (x < y) ? (x) : (y); } inline void add(int x, int y, ll z) { e[++KK] = (node){z, y, le[x]}; le[x] = KK; e[++KK] = (node){z, x, le[y]}; le[y] = KK; } inline void add_(int x, int y) { e_[++KK_] = (node){0, y, le_[x]}; le_[x] = KK_; } void dfs(int now, int father) { deg[now] = deg[father] + 1; fa[now][0] = father; dfn[now] = ++tmpp; for (int i = le[now]; i; i = e[i].nxt) if (e[i].to ^ father) { minv[e[i].to] = Min(minv[now], e[i].x);//求出割这个点最少要的费用(选 1 点到它费用最小的边) dfs(e[i].to, now); } } inline bool cmp(int x, int y) {//按 dfs 序排序 return dfn[x] < dfn[y]; } inline int LCA(int x, int y) {//求LCA if (deg[x] < deg[y]) swap(x, y); for (int i = 15; i >= 0; i--) if (deg[fa[x][i]] >= deg[y]) x = fa[x][i]; if (x == y) return x; for (int i = 15; i >= 0; i--) if (fa[x][i] ^ fa[y][i]) { x = fa[x][i]; y = fa[y][i]; } return fa[x][0]; } void build_kong() {//建虚树 tmpp = 0; sort(sp + 1, sp + sp[0] + 1, cmp); // nn = unique(sp + 1, sp + sp[0] + 1) - sp - 1; nn = sp[0]; sta[0] = 1; for (int i = 1; i <= nn; i++) { if (!tmpp) { sta[++tmpp] = sp[i]; t[sp[i]].fa = 0; } else { lca = LCA(sp[i], sta[tmpp]); while (deg[lca] < deg[sta[tmpp]]) { if (deg[lca] > deg[sta[tmpp - 1]]) { t[sta[tmpp]].fa = lca; } tmpp--; } while (lca ^ sta[tmpp]) { sp[++sp[0]] = lca; t[lca].fa = sta[tmpp]; sta[++tmpp] = lca; } t[sp[i]].fa = lca; sta[++tmpp] = sp[i]; } } // sort(sp + 1, sp + sp[0] + 1, cmp); for (int i = 1; i <= sp[0]; i++) if (sp[i] ^ 1) add_((t[sp[i]].fa) ? (t[sp[i]].fa) : 1, sp[i]); } void dfs_(int now, int father) {//树状 DP f[now] = minv[now];//这这里(那下面的都可以不割) ll tmp = 0; for (int i = le_[now]; i; i = e_[i].nxt) if (e_[i].to ^ father) { dfs_(e_[i].to, now); tmp += f[e_[i].to];//选把下面割个的都割掉,不割这里 } if (!real[now]) f[now] = Min(f[now], tmp);//如果这个这个点要割,那不能只割下面的,一定要选上面的割 le_[now] = 0; real[now] = 0; } int main() { // freopen("read.txt", "r", stdin); // freopen("write.txt", "w", stdout); n = read(); for (int i = 1; i < n; i++) { x = read(); y = read(); z = read(); add(x, y, z); } tmpp = 0; minv[1] = 1e18; dfs(1, 0); for (int i = 1; i <= 15; i++)//倍增预处理LCA for (int j = 1; j <= n; j++) fa[j][i] = fa[fa[j][i - 1]][i - 1]; m = read(); while (m--) { sp[0] = read(); for (int i = 1; i <= sp[0]; i++) { sp[i] = read(); real[sp[i]] = 1;//标记哪些点是一定要隔开的 } sp[++sp[0]] = 1; build_kong();//建虚树 dfs_(1, 0); write(f[1]); putchar('\n'); KK_ = 0; } return 0; }

__EOF__

本文作者あおいSakura
本文链接https://www.cnblogs.com/Sakura-TJH/p/YBT_JPDH_5-3-2.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   あおいSakura  阅读(24)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示