题意
给出一棵树,求一条路径,选择路上的V个点,使得被选择的点的相邻且不在路径上的点的权值和最大。
思路
定义:
c[i][j] c [ i ] [ j ] 为从 i i 点的子树中走到i,选择j个点的权值和。
为从 i i 点开始,向子树中走,选择j个点的权值和。
为 i i 的相邻节点的权值和。
为 i i 的父节点。
为 i i 的权值。
初始值:
b[x][0]=0,b[x][i]=g[x]−F[fa[x]] b [ x ] [ 0 ] = 0 , b [ x ] [ i ] = g [ x ] − F [ f a [ x ] ]
转移方程:
c[x][i]=max(c[y][i],c[y][i−1]+g[x]−F[y]) c [ x ] [ i ] = m a x ( c [ y ] [ i ] , c [ y ] [ i − 1 ] + g [ x ] − F [ y ] )
b[x][i]=max(b[y][i],b[y][i−1]+g[x]−F[fa[x]]) b [ x ] [ i ] = m a x ( b [ y ] [ i ] , b [ y ] [ i − 1 ] + g [ x ] − F [ f a [ x ] ] )
代码
#include<cstdio>
#include<string>
#include<cstring>
using namespace std;
const int maxn=1e5+5;
int N,V,x,y,tot,top,F[maxn],sta[maxn];
int son[maxn<<1],nxt[maxn<<1],lnk[maxn];
long long g[maxn],c[maxn][105],b[maxn][105],ans;
inline int read() {
int ret=0,f=1,ch=getchar();
for (; !isdigit(ch); ch=getchar()) if (ch=='-') f=-f;
for (; isdigit(ch); ch=getchar()) ret=ret*10+ch-48;
return ret*f;
}
inline void add_edge(int x,int y) {
son[++tot]=y,nxt[tot]=lnk[x],lnk[x]=tot;
son[++tot]=x,nxt[tot]=lnk[y],lnk[y]=tot;
}
inline void DP(int x,int y,int f) {
// ans一定要在前面赋值,这样可以避免从y走到x又走回y
for (int i=1; i<=V; ++i) ans=max(ans,c[x][i]+b[y][V-i]);
for (int i=1; i<=V; ++i)
c[x][i]=max(c[x][i],max(c[y][i],c[y][i-1]+g[x]-F[y])),
b[x][i]=max(b[x][i],max(b[y][i],b[y][i-1]+g[x]-F[f]));
}
void dfs(int x,int pre) {
for (int i=1; i<=V; ++i) c[x][i]=g[x],b[x][i]=g[x]-F[pre];
for (int k=lnk[x]; k; k=nxt[k]) if (son[k]^pre) dfs(son[k],x),DP(x,son[k],pre);
// 对于节点x,y和z是它的子节点并且,y先于z遍历。
// 上面的方法,无法计算z->x->y所以要倒着做一遍
for (int i=1; i<=V; ++i) c[x][i]=g[x],b[x][i]=g[x]-F[pre];
top=0;for (int k=lnk[x]; k; k=nxt[k]) if (son[k]^pre) sta[++top]=son[k];
for (int i=top; i; --i) DP(x,sta[i],pre);
ans=max(ans,max(c[x][V],b[x][V]));
}
int main() {
N=read(),V=read();
for (int i=1; i<=N; ++i) F[i]=read();
for (int i=1; i<N; ++i) add_edge(x=read(),y=read()),g[x]+=F[y],g[y]+=F[x];
return dfs(1,0),printf("%lld",ans),0;
}