【bzoj4987】Tree 树形背包dp
题目描述
从前有棵树。
找出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; }