【树形背包】BZOJ4987 Tree

题面在这里

首先有以下两个结论:

  • 选的点一定是一个连通子树
  • 答案是这个连通子树的边权和*2-直径

\(f_{i,j,0/1/2}\)表示\(i\)子树内,选了大小为\(j\)的连通子树(\(i\)在其中),包含了0/1/2个直径端点

大力树形背包即可,如果怕更新出问题就开临时数组搞把

示例程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include<cstdio>
#include<cstring>
#include<algorithm>
#define cl(x,y) memset(x,y,sizeof(x))
using namespace std;

const int maxn=3005,maxe=6005;
int n,K;
int son[maxe],nxt[maxe],lnk[maxn],w[maxe],tot;
inline void add(int x,int y,int z){
son[++tot]=y;nxt[tot]=lnk[x];lnk[x]=tot;w[tot]=z;
}
int f[maxn][maxn][3],t[maxn][3],siz[maxn],ans=0x3f3f3f3f;
void dfs(int x,int fa){
siz[x]=1;
f[x][1][0]=f[x][1][1]=f[x][1][2]=0;
for (int j=lnk[x];j;j=nxt[j])
if (son[j]!=fa){
dfs(son[j],x);
cl(t,63);
for (int i=0;i<=siz[x];i++)
for (int k=0;k<=siz[son[j]];k++){
t[i+k][0]=min(t[i+k][0],f[x][i][0]+f[son[j]][k][0]+2*w[j]);
t[i+k][1]=min(t[i+k][1],f[x][i][1]+f[son[j]][k][0]+2*w[j]);
t[i+k][1]=min(t[i+k][1],f[x][i][0]+f[son[j]][k][1]+w[j]);
t[i+k][2]=min(t[i+k][2],f[x][i][1]+f[son[j]][k][1]+w[j]);
t[i+k][2]=min(t[i+k][2],f[x][i][0]+f[son[j]][k][2]+2*w[j]);
t[i+k][2]=min(t[i+k][2],f[x][i][2]+f[son[j]][k][0]+2*w[j]);
}
siz[x]+=siz[son[j]];
for (int i=0;i<=siz[x];i++)
for (int k=0;k<3;k++)
f[x][i][k]=min(f[x][i][k],t[i][k]);
}
ans=min(ans,f[x][K][2]);
}
int main(){
scanf("%d%d",&n,&K);
for (int i=1,x,y,z;i<n;i++) scanf("%d%d%d",&x,&y,&z),add(x,y,z),add(y,x,z);
cl(f,63);
dfs(1,1);
printf("%d",ans);
return 0;
}