树形dp小结

这些天做了一些树形dp的题目,感觉有了些领悟,尤其是理解到树形背包就是分组背包之后。。选出几道不错的总结一下

hdu 1520

hdu 4003

poj 1155

poj 2486

hdu 4313

hdu 4340


hdu 1520
入门水题
每个节点有权值,子节点和父节点不能同时选,问最后能选的最大价值是多少
dp[i][0]表示第i个节点不选,dp[i][1]表示第i个节点选
dp[i][0] = sum(max(dp[j][1],dp[j][0]))
dp[i][1] = sum(dp[j][0]) + w[i]
hdu 2196
求树每个点到其他点的最远距离
两次dfs,来源分别是子节点和父节点
第一次,把树变为有根树,fir[i],sec[i],表示第i个点到叶子节点的最长距离和次长距离
第二次,考虑来源可以是父节点,再次跟新fir[],sec[]


hdu 4003
一棵有权树,从根结点中放入K个机器人,求用这K个机器人遍历所有的结点最少的权值和 
这题确实是个好题
刚开始很容易想到dp[i][j]表示用j个机器人遍历第i个节点为根的子树的最小花费。
然后父节点的状态由子节点更新,每个子节点下子树的机器人是可以是0-j,这就是一个分组背包的过程~。其中难点是看出一个性质,就是如果如果最终的状态中以i为根结点的树中有j(j>0)个机器人,那么不可能有别的机器人到了这棵子树后又跑到别的树中去,因为那样的话,一定会比j中的某一个到达i后跑与其相同的路径再回到i,再接着跑它的路径要差(多了一条i回去的边).所以每棵子树只有两种策略。1.由一个机器人遍历子树然后返回。2.k个机器人遍历,而且不返回。

void tree_dp(int u,int fa)
{
	for(int i = h[u]; i != -1; i = edge[i].next){
		int v = edge[i].v;
		int w = edge[i].w;
		if(v == fa) continue;
		tree_dp(v,u);
		for(int j = K; j > 0; j--){
			//1.由一个机器人遍历子树然后返回。
			dp[u][j] = dp[u][j] + dp[v][0] + 2*w; //用于更新的dp[u][j]代表的被前面子节点子树过的值
			//2.k个机器人遍历,而且不返回。
			for(int k = 1; k <= j; k++){
				dp[u][j] = min(dp[u][j],dp[u][j-k] + dp[v][k] + k*w);
			}	
		}
		dp[u][0] += dp[v][0] + 2*w;
	}
}




poj 1155
最基础的树形背包。但是写的好搓。。
dp[u][j] 节点u,发送j个客户,最大收益
void tree_dp(int u,int fa)
{
	bool isleaf = true;
	dp[u][0] = 0;
	for(int i = h[u]; i != -1; i = edge[i].next){
		int v =  edge[i].v;
		int cost = edge[i].w;
		if(v == fa) continue;
		tree_dp(v,u);
		amt[u] += amt[v];
		isleaf = false;
		//if(u == 3 && v == 6) pf(dp[3][2]);
		for(int j = amt[u]; j >= 1; j--)
			for(int k = 1; k <= amt[v] && k <= j; k++){
				//if(dp[v][k] == -inf || dp[u][j-k] == -inf) continue;
				dp[u][j] = max(dp[u][j],dp[u][j-k]+dp[v][k]-cost);
			}
	}
	if(isleaf){
		amt[u] = 1;
		dp[u][1] = w[u];
	}
}


poj 2486
很不错的树形背包模型
给定一棵节点数为n的树,每个节点都放有一些苹果,现在从根节点1开始走,每走一条边算一步,每经过一个节点就能吃掉这个节点的苹果(吃掉就没了),问走m步最多能吃几个苹果?
这题特殊的地方在于,可以回到根节点也可以不会到根节点。
//最终回到根节点
dp[u][j][0] = max(dp[u][j][0],dp[u][j-2-k][0] + dp[v][k][0]);
//从前面的兄弟节点回到根节点,然后到此子节点不会到根节点
dp[u][j][1] = max(dp[u][j][1],dp[u][j-1-k][0] + dp[v][k][1]);
//从此子节点回到根节点,但是会在后面的兄弟节点下去不会来
dp[u][j][1] = max(dp[u][j][1],dp[u][j-2-k][1] + dp[v][k][0]);

hdu 4313 Matrix 树形dp
对一颗节点数为n的树,所有节点分为,危险点和安全点两种。每条边都有边权,现在要求删掉一些边,把这颗树分成k棵子数,且保证每个子数中最多包含一危险点,问删掉的边的最小边权总和。
首先可以看出两个无需决策的选择。
1.如果根节点是危险点,那么其子树中就不能有危险点。
2.如果根节点点是安全点,且其子树中有危险点,则其必须要连一个危险点。
然后,我们要选择的只是当2中子孙节点中有多个危险点是,我们该选那一个。
显然对于一颗数,只有一个危险点,对于其中每一个节点,要么危险点和自己的关系有三种
dp[u][1] 表示当前节点为u,危险点在子孙节点,或是自己
dp[u][0] 表示当前节点为u,危险点在父节点中
状态表示好了,不难得出转移方程。
当u是危险点时 
dp[u][0] = inf;
dp[u][1] = sum(min(dp[s][1]+w[u][s],dp[s][0]))
当u是安全点时
dp[u][0] = sum(min(dp[s][1]+w[u][s],dp[s][0])) //子节点这中不会有危险点。
dp[u][1] = min(dp[u][0] - min(dp[s][1]+w[u][s]) + dp[s][1])


hdu 4340 Capturing a country 树形dp
这题解法和hdu 4313类似
对树中每个节点来说,其所在染色集的完整费用点要么在它上面,要么是它或其子孙节点,即三种来源
dp[i][j][0] 表示第i个点染j色且完整费用点在其上面
dp[i][j][1] 表示第i个点染j色且完整费用点是其本身或其其子孙节点
 * S = sum(min(dp[s][j][0],dp[s][1-j][1]))
 * dp[i][j][0] = cost[i][j]/2 + S; 
 * dp[i][j][1] = min(cost[i][j]+S,cost[i][j]/2 + min(dp[s][j][1]-min(dp[s][j][0],dp[s][1-j][1]));


你可能感兴趣的:(ACM)