zoukankan      html  css  js  c++  java
  • 【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
    " , ans);
    	return 0;
    }
    
  • 相关阅读:
    linux常用命令
    Python 父类调用子类方法
    import win32api 安装pip install pypiwin32
    Python 封装DTU-215码流卡 第一天
    git apply -v 提示 Skipped patch 打不上patch的解决办法
    2019/10/29
    12/9/2019
    11/9/2019
    9/7/2019
    人生若有命中注定
  • 原文地址:https://www.cnblogs.com/GXZlegend/p/7559627.html
Copyright © 2011-2022 走看看