题目描述
从前有棵树。
找出K个点A1,A2,…,Ak。
使得∑dis(AiAi+1),(1<=i<=K-1)最小。
输入
第一行两个正整数n,k,表示数的顶点数和需要选出的点个数。
接下来n-l行每行3个非负整数x,y,z,表示从存在一条从x到y权值为z的边。
I<=k<=n。
l<x,y<=n
1<=z<=10^5
n <= 3000
输出
一行一个整数,表示最小的距离和。
样例输入
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
样例输出
184524
题解
树形背包dp
先考虑几个显而易见的性质:
1.选出的点一定是相邻的
2.对于选出的点,如果从$a_k$再走回$a_1$,那么就相当于每条边经过了两次
由于题目没有包含$dis(a_k,a_1)$,因此就相当于选出的点中的一条链可以只经过一次,其余的需要经过两次。
那我们就可以将选点转化为选边,然后考虑树形背包:
设$f[i][j][k]$表示以$i$为根的子树中选择点$i$,共选出$j$条边,且包含的链端点数目为$k$的最小代价。
这里解释一下:当$k=0$时,相当于要从根节点遍历一遍选出的边然后再从根节点出去;$k=1$时,相当于从根节点遍历一遍,到达某链端点后不出去;$k=2$时相当于从某端点遍历到根节点,然后出去再回来到另一端点。
对于根节点与子节点之间的边,显然当$k=0$或$2$时计算两遍,否则计算一遍。
这里第二维和第三维都满足背包性质,然后就可以树形背包了。
时间复杂度$\Theta(4.5n^2)$
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 3010
using namespace std;
int head[N] , to[N << 1] , len[N << 1] , next[N << 1] , cnt , si[N] , f[N][N][3];
inline void add(int x , int y , int z)
{
to[++cnt] = y , len[cnt] = z , next[cnt] = head[x] , head[x] = cnt;
}
void dfs(int x , int fa)
{
int i , j , k , l , m;
si[x] = 1 , f[x][0][0] = f[x][0][1] = 0;
for(i = head[x] ; i ; i = next[i])
{
if(to[i] != fa)
{
dfs(to[i] , x);
for(j = si[x] - 1 ; ~j ; j -- )
for(k = si[to[i]] - 1 ; ~k ; k -- )
for(l = 2 ; ~l ; l -- )
for(m = l ; ~m ; m -- )
f[x][j + k + 1][l] = min(f[x][j + k + 1][l] , f[x][j][l - m] + f[to[i]][k][m] + len[i] * (2 - (m & 1)));
si[x] += si[to[i]];
}
}
}
int main()
{
int n , k , i , j , x , y , z , ans = 1 << 30;
scanf("%d%d" , &n , &k);
for(i = 1 ; i < n ; i ++ )
scanf("%d%d%d" , &x , &y , &z) , add(x , y , z) , add(y , x , z);
memset(f , 0x3f , sizeof(f));
dfs(1 , 0);
for(i = 1 ; i <= n ; i ++ )
for(j = 0 ; j <= 2 ; j ++ )
ans = min(ans , f[i][k - 1][j]);
printf("%d\n" , ans);
return 0;
}