[换根DP]luogu P3647 [APIO2014]连珠线

题面

https://www.luogu.com.cn/problem/P3647

不重复地取树中相邻的两条边,每次得分为两条边权和,问最大得分

分析

容易想到状态 f[i][0/1] 分别表示 i 号点不作为/作为两条边的经过点的以 i 为根子树中的最大得分

显然有 $f[i][0]=/sum max(f[j][0],f[j][1]+w_{i,j})$

又因为显然只可能有一对相邻边穿过 i ,转移时减去 f[i][0] 中 j 的贡献再加上连接 i,j 的贡献即可

$f[i][1]=f[i][0]-max{f[j][0]+w_{i,j}-max(f[j][0],f[j][1]+w_{i,j}}$

换根也很容易, f[i][0] 的转移是简单加法,所以减去加上贡献即可

f[i][1]的转移包含了最大值,套路记录次大值即可,注意转移时父亲的贡献

代码

#include <iostream>
#include <cstdio>
using namespace std;
const int Inf=2147483647;
const int N=2e5+10;
struct Graph {
    int v,w,nx;
}g[2*N];
int cnt,list[N];
int n,ans,f[N][2],mx[N][2];

void Add(int u,int v,int w) {g[++cnt]=(Graph){v,w,list[u]};list[u]=cnt;}

void DFS(int u,int fa) {
    mx[u][0]=mx[u][1]=f[u][1]=-Inf;
    for (int i=list[u],val;i;i=g[i].nx)
        if (g[i].v!=fa) {
            DFS(g[i].v,u);
            f[u][0]+=max(f[g[i].v][0],f[g[i].v][1]+g[i].w);
            val=f[g[i].v][0]+g[i].w-max(f[g[i].v][0],f[g[i].v][1]+g[i].w);
            if (mx[u][0]<val) mx[u][1]=mx[u][0],mx[u][0]=val;
            else mx[u][1]=max(mx[u][1],val);
        }
    f[u][1]=f[u][0]+mx[u][0];
}

void DFS(int u,int fa,int faw) {
    if (fa) f[u][0]+=max(f[fa][0],f[fa][1]+faw);
    ans=max(ans,f[u][0]);
    for (int i=list[u],a,b;i;i=g[i].nx)
        if (g[i].v!=fa) {
            a=f[u][0];b=f[u][1];
            f[u][0]-=max(f[g[i].v][0],f[g[i].v][1]+g[i].w);
            f[u][1]=f[u][0]+((mx[u][0]==f[g[i].v][0]+g[i].w-max(f[g[i].v][0],f[g[i].v][1]+g[i].w))?mx[u][1]:mx[u][0]);
            if (fa) f[u][1]=max(f[u][1],f[u][0]+f[fa][0]+faw-max(f[fa][0],f[fa][1]+faw));
            DFS(g[i].v,u,g[i].w);
            f[u][0]=a;f[u][1]=b;
        }
}

int main() {
    scanf("%d",&n);
    for (int i=1,u,v,w;i<n;i++) scanf("%d%d%d",&u,&v,&w),Add(u,v,w),Add(v,u,w);
    DFS(1,0);DFS(1,0,0);printf("%d\n",ans);
}
View Code

 

posted @ 2021-03-25 13:36  Vagari  阅读(36)  评论(0编辑  收藏  举报