解题思路:树形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,在第一榜排名前几,还算不错。
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原创,但可以转载,因为我们是兄弟。