HDU4003_Find_Metal_Mineral_树形DP分组

额- -其实也是看的别人的题解才做出来的,不过自己完完整整的写了一遍加详尽注释,感觉理解的透彻了些,又给同级的同学们讲了一遍,总之收获还是蛮大的。

写的代码是交的一道DIY的改编题,里面数据被肖神变态加强了,但是时间还是跟原题一样(T_T)导致我用Vector建树被卡时间了,后来开2000ms的试了一下1078ms- -瞬间无语,只能说肖神编的数据太恰到好处了,所以当时没办法就照着模版写了用结构加数组的邻接表建树,才800ms+水过,不过目前来说自己还是比较熟悉用Vector建树的,两段代码都贴上来,只有建树不同,里面的soldiers就是原题中的机器人

核心就是树形DP+每组必选一个的分组背包,状态难想,尤其是dp[i][0]的含义,基本都在注释中了

Find Metal Mineral

Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65768/65768 K (Java/Others)
Total Submission(s): 1796 Accepted Submission(s): 800


Problem Description
Humans have discovered a kind of new metal mineral on Mars which are distributed in point‐like with paths connecting each of them which formed a tree. Now Humans launches k robots on Mars to collect them, and due to the unknown reasons, the landing site S of all robots is identified in advanced, in other word, all robot should start their job at point S. Each robot can return to Earth anywhere, and of course they cannot go back to Mars. We have research the information of all paths on Mars, including its two endpoints x, y and energy cost w. To reduce the total energy cost, we should make a optimal plan which cost minimal energy cost.

Input
There are multiple cases in the input.
In each case:
The first line specifies three integers N, S, K specifying the numbers of metal mineral, landing site and the number of robots.
The next n‐1 lines will give three integers x, y, w in each line specifying there is a path connected point x and y which should cost w.
1<=N<=10000, 1<=S<=N, 1<=k<=10, 1<=x, y<=N, 1<=w<=10000.

Output
For each cases output one line with the minimal energy cost.

Sample Input
  
  
3 1 1 1 2 1 1 3 1 3 1 2 1 2 1 1 3 1

Sample Output
  
  
3 2
Hint
In the first case: 1->2->1->3 the cost is 3; In the second case: 1->2; 1->3 the cost is 2;


这是我用Vector建树的代码


Run ID

Submit Time

Judge Status

Pro.ID

Exe.Time

Exe.Memory

Code Len.

Language

Author

8538566

2013-07-07 12:07:48

Accepted

4003

593MS

7604K

2711 B

C++

算法·成诚


#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
#define N 100010
using namespace std;
int n,root,soldiers;
typedef struct MyStruct
{
	int child;
	int val;
}NODE;
vector<NODE>f[N];//f[N]存父亲节点的子节点

int dp[N][12];//dp[i][j]定义状态i节点的子树上分配j个士兵所需要花费的最小权值
//cost[i]存从根节点到i结点需要花费的权值
int parent[N];//建树需要
int num_child[N];//记录每个结点儿子的个数
int find_min(int a,int b)
{
	if (a<b)return a;
	else return b;
}

void find_dp(int x)
{
	int i,j,k,son;
	for ( i = 0; i < num_child[x]; i++)//如果到最底层的结点没有子节点,直接跳过,因为遍历以最底层结点为根的子树所消耗的权值为0
	{
		son=f[x][i].child;//son是每次遍历到的子结点
		if (parent[x]!=son)//保证每次遍历是自己的真儿子
		{
			parent[son]=x;//建立父子关系
			find_dp(son);//只要还有子节点,先更新子节点的值
			for ( j = soldiers; j >=0 ; j--)//这里j表示分配给x结点的士兵个数,因为是分组背包并且是压缩维数的,j相当于是花费,所以需要逆序
			{
				dp[x][j]=dp[x][j]+  dp[son][0]+  2*f[x][i].val;//dp[son][0]这个状态非常关键,表示派出去的一个士兵还要在回到父亲节点x,解决了士兵数量不足时需要让一个士兵遍历完一片子树后还需要去探索下一片区域时的问题
				//将本组中分配0个士兵这一“物品”先放入背包,保证每组有且只有一个物品在背包中,而且所有的分配方案中,dp[son][0]永远存在
				//因为当派0个结点时,派出去的一个士兵还要在回到父亲节点x,所以从x到son要走两边,所以是2*f[x][i].val
				for ( k = 1; k <=j ; k++)//现在是分组背包的第三维,也就是每组中挑一个最佳物品,但是当前组的物品个数又取决于所分配的士兵个数
				{//之所以从1开始循环,是因为之前k=0的情况已经被放进背包中了
					dp[x][j]=find_min( dp[x][j] ,dp[son][k]+k*f[x][i].val+dp[x][j-k]);//注意这里少一维i的变化,所以两个dp[x][j]一个是i层的,一个是i-1层的,含义不同
					//另外,核心是要么这组就取第一个那个dp[son][0]这一物品,要么取dp[son][k]这一物品,同时分配给dp[x][j]中的j个士兵被这个物品占用k个士兵,所以只能有dp[x][j-k]这一子状态
					//k*f[x][i].val表示把k个士兵从父亲节点x派到子节点son时需要的花费,而且不需要往返
				}
			}

		}
	}

}

int main()
{
	int i,j,k,a,b,c;
	NODE temp;
	while (scanf("%d%d%d",&n,&root,&soldiers)!=EOF)
	{
		for ( i = 1; i <=n; i++)
		{
			f[i].clear();
		}
		memset(dp,0,sizeof(dp));//不知道把dp数组初始化为0是否合理
		memset(parent,-1,sizeof(parent));
		memset(num_child,0,sizeof(num_child));
		for ( i = 1; i <=n-1 ; i++)
		{
			scanf("%d%d%d",&a,&b,&c);//双向存树,因为不知道谁是父亲,谁是儿子,只能是之后自己遍历的时候才能确定
			temp.child=b;temp.val=c;
			f[a].push_back(temp);num_child[a]++;
			temp.child=a;
			f[b].push_back(temp);num_child[b]++;
		} 

		find_dp(root);
		printf("%d\n",dp[root][soldiers]);
	}
	return 0;
}


下面是用邻接表建树的代码,自己还不是很懂,用的模版写的#include<cstdio>

Run ID

Submit Time

Judge Status

Pro.ID

Exe.Time

Exe.Memory

Code Len.

Language

Author

8538570

2013-07-07 12:08:13

Accepted

4003

484MS

6912K

2815 B

C++

算法·成诚


#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100010
using namespace std;
int n,root,soldiers;
typedef struct MyStruct
{
	int u,v;
	int val;
}NODE;
NODE edge[N];
int head[N];
int count_num;

int dp[N][12];//dp[i][j]定义状态i节点的子树上分配j个士兵所需要花费的最小权值
//cost[i]存从根节点到i结点需要花费的权值
int parent[N];//建树需要
int num_child[N];//记录每个结点儿子的个数
int find_min(int a,int b)
{
	if (a<b)return a;
	else return b;
}

void add_edge(int a,int b,int c)
{
	edge[count_num].u=b;//利用一个head数组和一个结构数组来建立双向树,没看懂,照着模版写的- -
	edge[count_num].v=head[a];
	edge[count_num].val=c;
	head[a]=count_num++;


	edge[count_num].u=a;
	edge[count_num].v=head[b];
	edge[count_num].val=c;
	head[b]=count_num++;
}

void find_dp(int x)
{
	int i,j,k,son;
	for ( i = head[x]; i!=-1;i=edge[i].v)//如果到最底层的结点没有子节点,直接跳过,因为遍历以最底层结点为根的子树所消耗的权值为0
	{
		son=edge[i].u;//son是每次遍历到的子结点
		if (parent[x]!=son)//保证每次遍历是自己的真儿子
		{
			parent[son]=x;//建立父子关系
			find_dp(son);//只要还有子节点,先更新子节点的值
			for ( j = soldiers; j >=0 ; j--)//这里j表示分配给x结点的士兵个数,因为是分组背包并且是压缩维数的,j相当于是花费,所以需要逆序
			{
				dp[x][j]=dp[x][j]+  dp[son][0]+  2*edge[i].val;//dp[son][0]这个状态非常关键,表示派出去的一个士兵还要在回到父亲节点x,解决了士兵数量不足时需要让一个士兵遍历完一片子树后还需要去探索下一片区域时的问题
				//将本组中分配0个士兵这一“物品”先放入背包,保证每组有且只有一个物品在背包中,而且所有的分配方案中,dp[son][0]永远存在
				//因为当派0个结点时,派出去的一个士兵还要在回到父亲节点x,所以从x到son要走两边,所以是2*f[x][i].val
				for ( k = 1; k <=j ; k++)//现在是分组背包的第三维,也就是每组中挑一个最佳物品,但是当前组的物品个数又取决于所分配的士兵个数
				{//之所以从1开始循环,是因为之前k=0的情况已经被放进背包中了
					dp[x][j]=find_min( dp[x][j] ,dp[son][k]+k*edge[i].val+dp[x][j-k]);//注意这里少一维i的变化,所以两个dp[x][j]一个是i层的,一个是i-1层的,含义不同
					//另外,核心是要么这组就取第一个那个dp[son][0]这一物品,要么取dp[son][k]这一物品,同时分配给dp[x][j]中的j个士兵被这个物品占用k个士兵,所以只能有dp[x][j-k]这一子状态
					//k*f[x][i].val表示把k个士兵从父亲节点x派到子节点son时需要的花费,而且不需要往返
				}
			}

		}
	}

}

int main()
{
	int i,j,k,a,b,c;
	NODE temp;
	while (scanf("%d%d%d",&n,&root,&soldiers)!=EOF)
	{
	    count_num=0;
		memset(dp,0,sizeof(dp));//不知道把dp数组初始化为0是否合理
		memset(parent,-1,sizeof(parent));
		memset(edge,0,sizeof(edge));
		memset(head,-1,sizeof(head));
		for ( i = 1; i <=n-1 ; i++)
		{
			scanf("%d%d%d",&a,&b,&c);//双向存树,因为不知道谁是父亲,谁是儿子,只能是之后自己遍历的时候才能确定
			add_edge(a,b,c);
		}

		find_dp(root);
		printf("%d\n",dp[root][soldiers]);
	}
	return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值