1. 程式人生 > >【bzoj4987】Tree 樹形背包dp

【bzoj4987】Tree 樹形背包dp

sam data name esp 沒有 距離 否則 由於 我們

題目描述

從前有棵樹。 找出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;
}

【bzoj4987】Tree 樹形背包dp