树形DP--HihoCoder1063 Travel On a Tree

#1063 : Travel on a Tree

Time Limit: 12000ms
Case Time Limit: 1000ms
Memory Limit: 256MB

Description

You have been given a weighted tree which has n nodes. Each node has a value vi and each edge has a length wi. You start at node 1, and can move a distance of d at most. How many values you can gather at most? For each node you can pick the value once no matter how many times you pass it.

Input

The first line of the input contains one integer n (1 ≤ n ≤ 100).

The second line contains n numbers, each of them denotes the value of one node: vi (0 ≤ vi ≤ 2).

For the next n-1 lines, each line contains 3 numbers (ai, bi, wi), representing an edge between ai and bi of length wi (1 ≤ ai, bi ≤ n, 1 ≤ wi ≤ 104).

The next line has a number q, representing the total number of queries (0 ≤ q ≤ 100000). The next q lines, each line contains a number d representing the distance you can move at most.

Output

For each query output the maximum values you can gather in one line.

Sample Input
3
0 1 1
1 2 5
1 3 3
3
3
10
11
Sample Output
1
1
2

        题目的大意是一棵以1为根节点的树,每一个节点都有一个权价值v,每条边有一个距离d。玩家从根节点出发,最大能行进的总路程不能超过l,问能达到的最大价值是多少。每一个节点的价值不可以重复获取。


        很明显的树形动态规划题目。我拿过来的时候瞬间想的就是dp[i][j]表示以i为根节点的子树,用不超过j的距离能达到的最大价值。表面上看简直是天衣无缝,有一点动态规划基础的朋友马上就能根据这个状态列出方程了。然而马上想到了一个问题:每一条边的距离是10000,总共有100条边。内存挺宽容没什么问题,但是转移起来不TLE才怪。


        完了完了完了完了,第一个也是最有感觉的想法被枪毙了。滚回去读题吧。


        node: vi (0 ≤ vi ≤ 2). 


        卧槽?!


        姑且不论突破点是啥,反正肯定要从这里入手了(谁让你长得这么有特点呢)。每个点的价值只有0,1,2这三种情况,也就是说整棵树的价值加一起也就200!


        用肚脐眼也能看出来,我们完全可以设dp[i][j]表示以i的子树中,要想恰好(注意这两个字)达到j的价值,所需要走的最小距离。


        于是乎咱们开始拓展这个思路。状态是要转移起来的,像水流一样能够平稳、持续、有规律的流动的。但是如果我们这样设状态的话遭遇了一个问题:


        假如我们当前研究的节点是x。他的所有儿子的所有dp[son[x]][j]都已经求出来了,但是我们并不能推出dp[x][j]。因为在儿子的状态中,达到最小距离后玩家人在儿子子树的哪是根本不知道的。为了达到一个综合的最大值,玩家很有可能需要探索一个子树以后,再原路返回去。虽然返回去的过程不会得到任何价值(题目中说了,任何节点的价值最多只能被获取一次)


        因此我们考虑再增加一维,dp[i][j][k](k只可以取0和1)表示以i为根节点的子树,要想达到j的价值,并且在条件k的限制条件下所需要的最小距离。k取0的时候,条件就是玩家必须要返回到i节点。k取1的时候,玩家爱在哪里在哪里。


        这个状态转移方程比较复杂(关键是我不会在电脑上弄公式),所以先立个flag,有空(学会了)以后再补上。大家直接看源代码吧。


#include<iostream>
#include<stdio.h>
#include<vector>
#include<stdlib.h>
#include<string.h>
#include<algorithm>
#define inf 2147483647
using namespace std;

struct edge
{
	int to,len;
	edge(int x,int y) : to(x),len(y){}
};

vector<edge>e[105];
int vv[105],n,q,dp[2][105][210];

void dfs(int u,int v)
{
	dp[0][u][vv[u]] = dp[1][u][vv[u]] = 0;
	for(int i = 0;i<e[u].size();i++)//part1
	{
		int h = e[u][i].to,f = e[u][i].len;
		if(h == v)continue;
		dfs(h,u);
		for(int x = 200;x >= 0;x--)
		{
			if(dp[0][u][x] < inf)
			for(int y = 0;y <= 200 - x;y++)
			if(dp[0][h][y] < inf)
			dp[0][u][x + y] = min(dp[0][u][x + y],dp[0][u][x] + dp[0][h][y] + 2 * f);
		}
	}
	for(int i = 0;i<e[u].size();i++)//part2
	{
		int h = e[u][i].to,f = e[u][i].len;
		if(h == v)continue;
		int temp[205];
		for(int j = 0;j<=202;j++)
		temp[j] = inf;
		temp[vv[u]] = 0;
		for(int j = 0;j<e[u].size();j++)
		{
			int t = e[u][j].to,l = e[u][j].len;
			if(t == h || t == v)continue;
			for(int x = 200;x >= 0;x--)
			{
				if(temp[x] < inf)
				for(int y = 0;y <= 200 - x;y++)
				if(dp[1][t][y] < inf)
				temp[x + y] = min(temp[x + y],temp[x] + dp[0][t][y] + 2 * l);
			}
		}
		for (int x=200;x>=0;x--)
		if (temp[x]<inf)
		for (int y=0;y<=200-x;y++)
		if (dp[1][h][y]<inf)
		dp[1][u][x+y]=min(dp[1][u][x+y],temp[x]+dp[1][h][y]+f);     
	}
}

int main()
{
	cin>>n;
	int t1,t2,t3;
	for(int i = 0;i<=100;i++)
	for(int j = 0;j<=200;j++)
	dp[0][i][j] = dp[1][i][j] = inf;
	for(int i = 1;i<=n;i++)
	cin>>vv[i];
	for(int i = 1;i<n;i++)
	{
		cin>>t1>>t2>>t3;
		e[t1].push_back(edge(t2,t3));
		e[t2].push_back(edge(t1,t3));
	}
	dfs(1,0);
	cin>>n;
	int l;
 	while(n--)
  	{
  		int ans;
 	    cin>>l;
		for (ans=200;ans>=1;ans--)
        if (dp[1][1][ans]<=l) break;
        printf("%d\n",ans);
    }
    return 0;
}


        打比赛的时候memset一紧张没弄明白WA两发=V=基本功不够被教做人,果断上低端赋值措施。代码看着low了点,有一种弄弄的乡土气味有木有T  T。


        关于树形DP,这道题做完了以后有一种不一样的感觉。dfs(x,y)可以理解成“已经把(x,y)及其自状态彻底搞定”的意思。


        现在我们看一下part1,要求玩家回到当前节点的这个部分.在part1的循环里进行了递归操作,把当前节点的一个儿子给解决掉了(注意我强调的是一个,就这一个)。然后利用这一个儿子的取值来更新父亲在指定j下的最小值。父亲的状态在这个循环中可以理解为“考虑自己,和第0到当前正在被判断的这些儿子中,制定一个j能达到的最小值”。这样当这个父亲的所有儿子都被枚举完了以后,自然就表示以这个父亲为根节点的子树中制定j达到的最小值啦。切忌因“树形DP中资源分配的问题”而果断的选择多叉树转二叉树。那样反而会更乱。


       再看part2,不要求玩家回到节点的部分。为了算出一个在以x为根的子树中达到j的最小值并且不要求回去,需要在它的儿子中选出来一个“开拓者”,也就是最后走这个儿子的子树,然后不再回来。对于其他的子树(如果有的话)还是要求必须回来的。所以先建立一个temp数组,temp[j]表示的是“除去某个儿子不做考虑外,走其他的儿子子树,并且要求回来,达到恰好j的价值所需要的最小距离”(我发现树形动态规划的东西不管怎么说都很拗口有木有)。紧接着,将这个被排除出去的儿子作为开拓者,然后用和part1很类似的方法进行转移就是了。不过要注意由于开拓者不用回来,所以加上一倍的边长就行了。


        从这道题中我学到了很多。第一是树形DP的资源分配很多情况下用不上多叉树转二叉树;第二是要保持思维的全面性,否则容易在各种状态的海洋中迷失。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值