[换根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); }
在日渐沉没的世界里,我发现了你。