F.Maximum Weight Subset
显然是树形dp没错了,重点在于如何处理和如何转移.
我的做法是:
dp[i][j] 表示距结点 i 最近为 j 的点集的最大权重和.
每次由叶子结点往父亲节点转移,很明显对于父亲结点 u, 叶子结点 v, dp[u][j] 最多只能有 一个 叶子结点可以 为 j,其他的节点必须是max(k-j,j),否则会导致选择的点集里面有距离大于 k 的点对.
同时dp[u][j] 还可以向 dp[u][j-1]转移,取最大值.
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define fir first
#define sec second
using namespace std;
const int maxn = 300;
const long long INF = 1e16;
int n,k,e;
int a[maxn];
int head[maxn],edge[maxn<<1],nex[maxn<<1];
long long dp[maxn][maxn];
long long Cg[maxn][maxn];
long long ans = 0;
void add(int l,int r) {
edge[++e] = r;
nex[e] = head[l];
head[l] = e;
}
void dfs(int u,int f) {
dp[u][0] = a[u];
for(int i=head[u];i;i=nex[i]) {
int v = edge[i];
if(v==f) continue;
dfs(v,u);
for(int i=0;i<=k;i++) {
long long val = dp[v][max(k-i-1,i-1)];
dp[u][i] += val;
Cg[u][i] = max(Cg[u][i],dp[v][i-1] - val);
}
}
for(int i=k;i>=0;i--) {
if(Cg[u][i] >= 0) dp[u][i] += Cg[u][i];
dp[u][i] = max(dp[u][i+1],dp[u][i]);
ans = max(ans,dp[u][i]);
//printf("dp[%d][%d] = %lld\n", u,i,dp[u][i]);
}
}
int main() {
scanf("%d%d",&n,&k);
k++;
for(int i=0;i<=n;i++) for(int j=0;j<=k;j++) Cg[i][j] = -INF;
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=0;i<n-1;i++) {
int l,r;
scanf("%d%d",&l,&r);
add(l,r);
add(r,l);
}
dfs(1,0);
printf("%lld\n",ans);
return 0;
}