bzoj4987: Tree(树形dp)

时间:2022-12-20 17:44:53

Description

从前有棵树。
找出K个点A1,A2,…,Ak。
使得∑dis(AiAi+1),(1<=i<=K-1)最小。
 

Input

第一行两个正整数n,k,表示数的顶点数和需要选出的点个数。
接下来n-l行每行3个非负整数x,y,z,表示从存在一条从x到y权值为z的边。
I<=k<=n。
l<x,y<=n
1<=z<=10^5
n <= 3000
 

Output

一行一个整数,表示最小的距离和。
 

Sample Input

10 7
1 2 35129
2 3 42976
3 4 24497
2 5 83165
1 6 4748
5 7 38311
4 8 70052
3 9 3561
8 10 80238

Sample Output

184524
 
 
首先,为了保证最优,选出的点相邻,而且对于每条边,如果要算上从$a_k$走到$a_1$的话,相当于每条边走了两遍
那么现在不用算$a_k$走到$a_1$,相当于是从这一个联通块里选出一条链去掉它的贡献,其余的边都要被计算两边,那么我们可以转化为选出一些边
考虑树形dp,设$f[i][j][k]$表示在第$i$个点及其子树中选出了$j$条边,$k$表示选边的状态,此时的最小代价
$k=0$表示从当前的根节点遍历一遍子树中选的边再回来,$k=1$,表示遍历完子树就不回来,$k=2$表示从子树中某一个点遍历到根节点再出去又回来到另一个端点
然后不难发现每一个数字代表的状态都能由数值加起来等于它的状态转移而来。比方说状态2,可以看做是由两个状态为1的状态拼起来得到
又发现当前根和子树之间的连边只有在$k=1$时算一遍,其他时候都算两遍
然后树形dp即可
 //minamoto
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define getc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
char buf[<<],*p1=buf,*p2=buf;
template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,:;}
int read(){
#define num ch-'0'
char ch;bool flag=;int res;
while(!isdigit(ch=getc()))
(ch=='-')&&(flag=true);
for(res=num;isdigit(ch=getc());res=res*+num);
(flag)&&(res=-res);
#undef num
return res;
}
const int N=;
int head[N],Next[N<<],ver[N<<],edge[N<<],tot;
inline void add(int u,int v,int e){
ver[++tot]=v,Next[tot]=head[u],head[u]=tot,edge[tot]=e;
}
int sz[N],f[N][N][],n,m,ans=<<;
void dfs(int u,int fa){
sz[u]=,f[u][][]=f[u][][]=;
for(int i=head[u];i;i=Next[i]){
int v=ver[i];
if(v!=fa){
dfs(v,u);
for(int j=sz[u]-;~j;--j)
for(int k=sz[v]-;~k;--k)
for(int l=;~l;--l)
for(int m=l;~m;--m)
cmin(f[u][j+k+][l],f[u][j][l-m]+f[v][k][m]+edge[i]*(-(m==)));
sz[u]+=sz[v];
}
}
}
int main(){
// freopen("testdata.in","r",stdin);
n=read(),m=read();
for(int i=,u,v,e;i<n;++i)
u=read(),v=read(),e=read(),add(u,v,e),add(v,u,e);
memset(f,0x3f,sizeof(f));dfs(,);
for(int i=;i<=n;++i) for(int j=;j<=;++j) cmin(ans,f[i][m-][j]);
printf("%d\n",ans);
return ;
}