【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;
}

 

posted @ 2017-09-20 08:08  GXZlegend  阅读(986)  评论(3编辑  收藏