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