Hdu 4003 Find Metal Mineral (DP_树形DP(背包))

题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=4003

题目大意:给定一棵n个节点的树,遍历每条数边都需要费用cost,现在给定k个机器人,要求用这个k个机器人遍历整棵树,使得经过的费用和最小,n<=10000.

解题思路:树形DP+分组背包,本题的分组背包不是一般性地至多选择一个物品而是必须选一个,且只能选一个,具体的稍后分析。这题想了挺久的,最开始想的是贪心解法,如果k为1,那么只要找出从叶子到根费用和最大的那条路径就好,拓展开来想找出前k大的路径是不是也能求解?答案是No,因为路径可以重叠,所以没办法这样做。这个想法否定了之后开始想着用动规的思想来解这题。

     由父节点的状态只从子节点转移而来可知这题无后效性,可用树形dp。每个节点有若干个子节点,每个子节点都可以选择0-j个机器人走(0的时候表示走这个分支的机器人已经在其他分支走过了),这样就可以转换个模型,有多少个子节点就有多少组物品,因为每个子节点都必须选,所以每组物品都必须选择一个,但是只能选一个,显然!

     设dp[i][j]表示在节点i用j个机器人遍历其所有子节点所花费的最小费用,由于每个节点都必须走,那么每个节点怎么走有两种情况:一种是机器人从这点走后不回到这点,一种是机器人从这点走后回到这点,因此用j = 0和 j > 0来表示。若干只机器人进入某分支后如果要回来那么只会有一只机器人回来。假设有两只从v节点下去,遍历完回来,花费的4*p->v->cost+遍历以p->v为根的树的费用,而一只只要2*p->v->vost+遍历以p->v为根的树的费用,比如边有(1,2)(2,3)(2,4),以1为根,那么一只的话每条边遍历两遍回到1点,两只的话(1,2)遍历了四遍。

    状态转移方程为:dp[i][j] = min(dp[i][j-1]+dp[son][0]+cost,dp[i][j]+dp[son][0]+2*cost)

                          dp[i][j] = min(dp[i][j],dp[son][k]+dp[i][j-k]+k*cost);//cost为边(i,son)费用,k为子节点的状态

    本题是典型的树形DP+背包,强烈推荐好好吸收。代码可不断优化,我代码中状态转移方程其实可以更简单些,但考虑这样写好理解些,代码跑了234ms,在第一榜排名前几,还算不错。


测试数据:
5 1 2
1 2 2
1 3 2
2 4 2
2 5 2

5 1 2
1 2 2
1 3 2
2 4 10
2 5 10

3 1 1
1 2 1
1 3 1

3 1 2
1 2 1
1 3 1

7 1 2
1 2 2
1 3 2
2 4 2
2 5 2
3 6 10
3 7 10

9 1 1
1 2 2
1 3 2
2 4 2
2 5 2
3 6 10
3 7 10
4 8 20
4 9 20

7 1 3

1 2 100
1 4 100
1 3 2
3 5 1
3 6 1
3 7 1

8 1 3
1 8 100
1 2 100
1 4 100
1 3 2
3 5 1
3 6 1
3 7 1

8 1 3
1 8 100
1 2 100
1 4 100
1 3 2
3 5 100
3 6 100
3 7 100

7 1 3
1 2 100
1 4 100
1 3 2
3 5 1000
3 6 100
3 7 1000


代码:
#include <stdio.h>
#include <string.h>
#define MAX 11000
#define min(a,b) (a)<(b)?(a):(b)


struct node {

	int v,cost;
	node *next;
}*head[MAX*2],tree[MAX*2];
int ptr,ans,root,vis[MAX];
int n,m,dp[MAX][12];


void Initial() {
	
	ptr = 1;
	memset(dp,0,sizeof(dp));
	memset(vis,0,sizeof(vis));
	memset(head,NULL,sizeof(head));
}
void AddEdge(int x,int y,int cost) {

	tree[ptr].v = y,tree[ptr].cost = cost;
	tree[ptr].next = head[x],head[x] = &tree[ptr++];
	tree[ptr].v = x,tree[ptr].cost = cost;
	tree[ptr].next = head[y],head[y] = &tree[ptr++];
}
void Tree_DP(int v) {

	if (vis[v]) return;
	vis[v] = 1;
	int i,j,fir,sec;
	node *p = head[v];
	

	while (p != NULL) {

		if (!vis[p->v]) {

			Tree_DP(p->v);
			for (j = m; j >= 1; --j) {

				fir = dp[v][j-1] + dp[p->v][0] + p->cost;	//分配一个机器人跑p->v这个分支,fir、sec选其一保证了本组背包必选一个物品
				sec = dp[v][j] + dp[p->v][0] + 2 * p->cost;	//跑其他分支的也顺带着跑p->v这个分支,并且就1个跑
				dp[v][j] = min(fir,sec);
				for (i = 1; i <= j; ++i)
					dp[v][j] = min(dp[v][j],dp[p->v][i]+dp[v][j-i]+i*p->cost);
			}
			dp[v][0] += dp[p->v][0] + 2 * p->cost;			//这就相当于附加计算,只有前四句会用这个,其实不分配机器人在这个分支跑
		}
		p = p->next;
	}
}


int main()
{
	int i,j,k,a,b,c;
	

	while (scanf("%d%d%d",&n,&root,&m) != EOF) {

		Initial();
		for (i = 1; i < n; ++i) {

			scanf("%d%d%d",&a,&b,&c);
			AddEdge(a,b,c);
		}


		Tree_DP(root);
		printf("%d\n",dp[root][m]);
	}
}


本文ZeroClock原创,但可以转载,因为我们是兄弟。

你可能感兴趣的:(c,优化,struct,tree,测试,null)