【bzoj4987】Tree 樹形背包dp
阿新 • • 發佈:2017-09-20
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