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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM