当前位置:   article > 正文

算法刷题记录(Day 53)_如何记录刷过的算法

如何记录刷过的算法

Apple Tree(poj 2486)

原题链接
题目类型:树形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]);
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74

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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

总结

1.树形dp问题,是含有两个dp过程的,一个是基于树的结构,另一个是在每一层之间
2.dp问题可以适当增加状态的表示,从而正确解题

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号