题意:
一棵树,每个节点有一个权值,求从根节点出发,最多走K步,能够取得的最大权值(可以往回走)。
思路:
dp[1][u][j] 从u节点出发,最多走j步(可以小于j步),并且返回到 u 节点,可以获得的最大权值。
dp[0][u][j] 从u节点出发,最多走j步,最终停留在u的子树节点,可以获得的最大权值。
关键是状态转移方程啊,涉及到停留在 u 点和停留在 v 点展开了优化讨论:
dp[1][u][i+2] = max(dp[1][u][i+2], dp[1][v][i-j] + dp[1][u][j]);
dp[0][u][i+2] = max(dp[0][u][i+2], dp[1][v][i-j] + dp[0][u][j]);
dp[0][u][i+1] = max(dp[0][u][i+1], dp[0][v][i-j] + dp[1][u][j]);
其中v是u节点的孩子节点,j range from K to 0(类似01背包)
代码中使用了一点小小的剪枝:由于没走到一个孩子节点,步数至少减少 1 的,所以可以逐步把步数减 1,最终代码跑到了 32ms.
#include <iostream>
#include <algorithm>
using namespace std;
const int MAXN = 110;
const int MAXD = 210;
structedge {
int v;
edge* next;
} *V[MAXN], ES[MAXN * 2];
int EC, N, K, val[MAXN], dp[2][MAXN][MAXD];
bool vis[MAXN];
void addedge(int u, int v)
{
ES[++EC].next = V[u];
V[u] = ES + EC; V[u]->v = v;
}
void initdata(int n)
{
EC = 0;
memset(V, 0, sizeof(V));
memset(vis, false, sizeof(vis));
for (int i = 1; i <= n; ++i)
scanf("%d", &val[i]);
for (int i = 1; i <= n - 1; ++i)
{
int a, b;
scanf("%d %d", &a, &b);
addedge(a, b);
addedge(b, a);
}
}
void treedp(int u, int vol)
{
vis[u] = true;
for (int j = 0; j <= vol; ++j)
dp[0][u][j] = dp[1][u][j] = val[u];
for (edge* e = V[u]; e; e = e->next)
{
int v = e->v;
if (vis[v])
continue;
treedp(v, vol - 1);
for (int i = vol; i >= 0; --i)
{
for (int j = 0; j <= i; ++j)
{
dp[1][u][i+2] = max(dp[1][u][i+2], dp[1][v][i-j] + dp[1][u][j]);
dp[0][u][i+2] = max(dp[0][u][i+2], dp[1][v][i-j] + dp[0][u][j]);
dp[0][u][i+1] = max(dp[0][u][i+1], dp[0][v][i-j] + dp[1][u][j]);
}
}
}
}
int main()
{
while (scanf("%d %d", &N, &K) != EOF)
{
initdata(N);
treedp(1, K);
printf("%d\n", dp[0][1][K]);
}
return 0;
}