[lnsyoj3677/luoguP3647]连珠线

题意

原题链接
给定 n 个点,需要在之间连接 n1 条边,每条边的颜色为红/蓝。连边规则为:设现在已经将 1i1 连接起来,则可以选择在 1i1 间的任意一个点与 i 连一条红边,或者将两个连接了红边的点 j,k 之间,将其红边删去,然后在 j,ik,i之间连接两条蓝边。求蓝边长度的最大值。

赛时 0PTS

赛后

本题为在树上求最大值,因此考虑树形 DP。显然,本题为一棵无根树,因此我们需要使用二次扫描与换根法进行处理。

朴素树形 DP

状态设计:fu,j 表示在以 1 为根节点的树中的以 u 为根节点的子树中,u 为蓝边中点(j=1)/ u 不为蓝边中点(j=0)的蓝边长度的最大值(注意:在这里,由于是一棵无根树,因此我们总是将两条蓝边置为 fauson 的形式,而不会出现 sonuson 的形式。
状态转移:
fu,0 较好计算,由于 u 不能作为蓝边的中点,因此,对于它的每个儿子 j 而言,要么 j 也不作为蓝边的中点,ij 是一条红边,此时最大值为 fj,0;要么 j 作为蓝边的中点,ij 是一条蓝边,此时最大值为 fj,1+wij。综上,

fu,0=jsonmax{fj,0,fj,1+wij}

根据题意,可以得到,u 最多只会作为一对蓝边的中点。因此,对于 fu,1,我们可以枚举每一条边,计算这条边为蓝边的最大值,即

fu,1=fu,0+maxjson(fj,0+wijmax{fj,0,fj,1+wij})

换根 DP

在朴素树形 DP 中,我们可以通过将不同的点作为根来进行暴力枚举,时间复杂度 O(n2)
为了将时间复杂度优化成 O(n),我们需要再开一个数组 gu,表示在以 u 为根节点的树中,蓝边长度的最大值。记 u 的父亲为 fa,我们观察以 1 为根节点和以 u 为根节点的两棵树,会发现转移方向从 ufa 变为了 fau,我们只需要再次进行 DFS 处理这一部分的更改就可以实现 O(n) 的换根 DP 了,这就是二次扫描与换根法
对于本题,我们需要计算以 fa 为根的树中,去除掉以 u 为根的子树后,fa 为蓝边中点 / fa 不为蓝边中点的蓝边长度的最大值,(将 ufa 间连接一条边),这需要我们计算在以 fa 为根的树中,转移到的最大值,次大值,和转移最大值的点。再以此计算以 u 为根的树中,g,最大值,次大值和转移最大值的点即可。

代码

#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;
}
posted @   是一只小蒟蒻呀  阅读(29)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示