[lnsyoj3677/luoguP3647]连珠线
题意
原题链接
给定 个点,需要在之间连接 条边,每条边的颜色为红/蓝。连边规则为:设现在已经将 连接起来,则可以选择在 间的任意一个点与 连一条红边,或者将两个连接了红边的点 之间,将其红边删去,然后在 、之间连接两条蓝边。求蓝边长度的最大值。
赛时 0PTS
赛后
本题为在树上求最大值,因此考虑树形 DP。显然,本题为一棵无根树,因此我们需要使用二次扫描与换根法进行处理。
朴素树形 DP
状态设计: 表示在以 为根节点的树中的以 为根节点的子树中, 为蓝边中点()/ 不为蓝边中点()的蓝边长度的最大值(注意:在这里,由于是一棵无根树,因此我们总是将两条蓝边置为 的形式,而不会出现 的形式。)
状态转移:
较好计算,由于 不能作为蓝边的中点,因此,对于它的每个儿子 而言,要么 也不作为蓝边的中点, 是一条红边,此时最大值为 ;要么 作为蓝边的中点, 是一条蓝边,此时最大值为 。综上,
根据题意,可以得到, 最多只会作为一对蓝边的中点。因此,对于 ,我们可以枚举每一条边,计算这条边为蓝边的最大值,即
换根 DP
在朴素树形 DP 中,我们可以通过将不同的点作为根来进行暴力枚举,时间复杂度 。
为了将时间复杂度优化成 ,我们需要再开一个数组 ,表示在以 为根节点的树中,蓝边长度的最大值。记 的父亲为 ,我们观察以 为根节点和以 为根节点的两棵树,会发现转移方向从 变为了 ,我们只需要再次进行 DFS 处理这一部分的更改就可以实现 的换根 DP 了,这就是二次扫描与换根法。
对于本题,我们需要计算以 为根的树中,去除掉以 为根的子树后, 为蓝边中点 / 不为蓝边中点的蓝边长度的最大值,(将 和 间连接一条边),这需要我们计算在以 为根的树中,转移到的最大值,次大值,和转移最大值的点。再以此计算以 为根的树中,,最大值,次大值和转移最大值的点即可。
代码
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 200005, M = 400005, INF = 2e9;
int h[N], e[M], w[M], ne[M], idx;
int n;
int f[N][2];
int maxn[N], maxid[N], smaxn[N];
int g[N];
int maxun[N], maxuid[N], smaxun[N];
void add(int a, int b, int c){
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
void dfs(int u, int fa){
maxn[u] = smaxn[u] = f[u][1] = -INF, maxid[u] = 0;
for (int i = h[u]; ~i; i = ne[i]){
int j = e[i];
if (j == fa) continue;
dfs(j, u);
f[u][0] += max(f[j][0], f[j][1] + w[i]);
}
for (int i = h[u]; ~i; i = ne[i]){
int j = e[i];
if (j == fa) continue;
if (f[j][0] + w[i] - max(f[j][0], f[j][1] + w[i]) > maxn[u]){
maxid[u] = j;
smaxn[u] = maxn[u];
maxn[u] = f[j][0] + w[i] - max(f[j][0], f[j][1] + w[i]);
}
else smaxn[u] = max(smaxn[u], f[j][0] + w[i] - max(f[j][0], f[j][1] + w[i]));
}
f[u][1] = f[u][0] + maxn[u];
}
void dfs2(int u, int fa, int val){
if (u == 1) g[u] = f[u][0], maxun[u] = maxn[u], maxuid[u] = maxid[u], smaxun[u] = smaxn[u];
else {
int ffa0 = g[fa] - max(f[u][0], f[u][1] + val);
int ffa1;
if (u != maxuid[fa]) ffa1 = ffa0 + maxun[fa];
else ffa1 = ffa0 + smaxun[fa];
g[u] = f[u][0] + max(ffa0, ffa1 + val);
maxuid[u] = maxid[u], maxun[u] = maxn[u], smaxun[u] = smaxn[u];
if (ffa0 + val - max(ffa0, ffa1 + val) > maxun[u]){
maxuid[u] = fa;
smaxun[u] = maxun[u];
maxun[u] = ffa0 + val - max(ffa0, ffa1 + val);
}
else smaxun[u] = max(smaxun[u], ffa0 + val - max(ffa0, ffa1 + val));
}
for (int i = h[u]; ~i; i = ne[i]){
int j = e[i];
if (j == fa) continue;
dfs2(j, u, w[i]);
}
}
int main(){
memset(h, -1, sizeof h);
scanf("%d", &n);
for (int i = 1; i < n; i ++ ){
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c), add(b, a, c);
}
dfs(1, -1);
dfs2(1, -1, 0);
int ans = 0;
for (int i = 1; i <= n; i ++ ) ans = max(ans, g[i]);
printf("%d\n", ans);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现