赞
踩
原题链接
题目类型:树形dp
思考过程:K是大于N的,这也就意味着一次的移动不仅仅只是邻接的。
->处理出任意两点之间的距离->lca问题 ->但是既然移动过来了,就一定会吃->K比N大是用于一棵子树的多条吃的路径的
有点类似于0、1背包的问题,value是苹果的个数,weight是两个节点之间的距离,若仅仅使用二维的dp来进行记录,dp[i][j]代表最后一步到节点i时走了j步的最大值,->这样会限制走的节点的标号是递增的。但是如果不将dp[i][j]的最后一步限制在i上,那么将无法进行递推下去。->可以增加一个数组来记录上一回的最后一个子树的长度吗?
树形递推呢?
dp[u][j]代表的是以u为根的树,限制步数为j时,最多能吃到的 苹果树。假设其子节点分别为v1,v2,v3…
对于v1,dp[u][j]初始化为dp[u][j]=dp[v1][j-1]+num[u]
对于v2,dp[u][j]=max(dp[u][i],dp[v2][p-2*k-1 ]+dp[u][k])(k=0,1,2,3…p-1)->注意,这里的递推如果简单进行乘2会出现问题,因为回撤的时候仅仅只是在最后的一棵子树上,简单的乘2会导致第一棵子树一直在来回走
超过了节点数怎么办?->把它全部设置为目前的最大的苹果数
//WA #include<iostream> using namespace std; #define NMAX 105 #define KMAX 210 int num[NMAX]; int is[NMAX]; int N, K; int dp[NMAX][KMAX]; struct Edge { int from, to, nxt; }E[2 * NMAX]; int head[NMAX], cnt = 1; void add(int u, int v) { E[cnt].from = u, E[cnt].to = v, E[cnt].nxt = head[u]; head[u] = cnt++; E[cnt].from = v, E[cnt].to = u, E[cnt].nxt = head[v]; head[v] = cnt++; } void dfs(int u) { int sub[KMAX]; is[u] = 1; dp[u][0] = num[u]; sub[0] = 0; int first = 1; for (int i = head[u]; i; i = E[i].nxt) { int v = E[i].to; if (!is[v]) { dfs(v); /*if (u == 1) { cout << "test" << endl; }*/ for (int j = K; j > 0; j--) { if (first) { dp[u][j] = dp[v][j - 1] + num[u]; sub[j] = j; } else { for (int k = 0; j - k - sub[k] - 1 >= 0; k++) { //dp[u][j] = max(dp[u][j], dp[u][k] + dp[v][j - 2 * k - 1]); if (dp[u][j] < dp[u][k] + dp[v][j - k - sub[k] - 1]) { dp[u][j] = dp[u][k] + dp[v][j - k - sub[k] - 1]; sub[j] = j - k - sub[k]; } } } } first = 0; } } if (first) { //填充完叶子节点 for (int i = 1; i <= K; i++) dp[u][i] = num[u]; } } int main() { while (scanf_s("%d %d", &N, &K) == 2) { memset(is, 0, sizeof(is)); memset(head, 0, sizeof(head)); memset(dp, 0, sizeof(dp)); cnt = 1; for (int i = 1; i <= N; i++) { scanf_s("%d", &num[i]); } for (int i = 0; i < N - 1; i++) { int u, v; scanf_s("%d %d", &u, &v); add(u, v); } dfs(1); printf("%d\n", dp[1][K]); } }
WA原因分析:
如上图所示的情况下,仅仅改变了节点2和3的输入顺序,却导致了结果的不同,这是因为,如果先输入3再输入2,根据上述的子树节点的递推关系,就没有先经过3再经过2的情况,即和正确的结果失之交臂。
思路的改进:
参考思路
思路的修正:
1.树形dp在一层上仍然可以再使用一种dp方法,对于这种dp的顺序有较大影响的(如在WA中分析的那样),需要去使用不同的数据结构来进行表示不同的状态。
2.dp[i][j][0]代表的是以i为根节点,最多走j步且必须回到i的最大苹果数
dp[i][j][1]代表的是以i为根节点,最多走j步且不必回到i的最大苹果数
首先全部初始华为0,然后将dp[i][j][0]和dp[i][j][1]初始化为节点i的苹果数
//AC #include<iostream> using namespace std; #define NMAX 105 #define KMAX 210 int num[NMAX]; int is[NMAX]; int N, K; int dp[NMAX][KMAX][2]; struct Edge { int from, to, nxt; }E[2 * NMAX]; int head[NMAX], cnt = 1; void add(int u, int v) { E[cnt].from = u, E[cnt].to = v, E[cnt].nxt = head[u]; head[u] = cnt++; E[cnt].from = v, E[cnt].to = u, E[cnt].nxt = head[v]; head[v] = cnt++; } void dfs(int u) { is[u] = 1; for (int i = 0; i <= K; i++) dp[u][i][0] = dp[u][i][1] = num[u]; for (int i = head[u]; i; i = E[i].nxt) { int v = E[i].to; if (!is[v]) { dfs(v); for (int j = K; j >= 0; j--) { for (int k = 0; k <= K; k++) { if (j - k - 2 >= 0) dp[u][j][0] = max(dp[u][j][0], dp[u][k][0] + dp[v][j - k - 2][0]); if (j - k - 2 >= 0) dp[u][j][1] = max(dp[u][j][1], dp[u][k][1] + dp[v][j - k - 2][0]); if (j - k - 1 >= 0) dp[u][j][1] = max(dp[u][j][1], dp[u][k][0] + dp[v][j - k - 1][1]); } } } } } int main() { while (scanf_s("%d %d", &N, &K) == 2) { memset(is, 0, sizeof(is)); memset(head, 0, sizeof(head)); memset(dp, 0, sizeof(dp)); cnt = 1; for (int i = 1; i <= N; i++) { scanf_s("%d", &num[i]); } for (int i = 0; i < N - 1; i++) { int u, v; scanf_s("%d %d", &u, &v); add(u, v); } dfs(1); printf("%d\n", dp[1][K][1]); } }
1.树形dp问题,是含有两个dp过程的,一个是基于树的结构,另一个是在每一层之间
2.dp问题可以适当增加状态的表示,从而正确解题
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。