树形DP+背包
题意:给一棵树,每个点有相应的价值,要求从节点1开始走最多k步,问能取得的最大价值。重复经过的点不算。
分析:这题的特点是走到叶子节点后回返也需要消耗步数。
思路:设状态g[i][j]表示以i为根的子树走j步不回到根的最大价值,状态b[i][j]表示以i为根的子树走j步最后回到根的最大价值
所以 b[u][i+j+2] = max(b[u][i+j+2], b[u][i]+b[v][j]);
然后g的求法是,因为对一个根来说,他的不返回的子树最多只有一个,所以枚举他的不返回子树,其他子树记返回即可。
略牛逼的状态啊,果断是看别人才懂的。。
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
int max(int a, int b) { return a>b?a:b; }
int min(int a, int b) { return a<b?a:b; }
const int MAXN = 210;
int g[MAXN][MAXN],b[MAXN][MAXN];
int tt[MAXN];
int n,k;
struct E{
int v,next;
}edge[MAXN<<1];
int head[MAXN],tot;
void add_edge(int a, int b)
{
edge[tot].next = head[a];
edge[tot].v = b;
head[a] = tot ++;
}
void dfs(int u, int fa)
{
int p,i,j;
int maxt;
for(p=head[u]; p!=-1; p=edge[p].next)
{
int v = edge[p].v;
if(v == fa) continue;
dfs(v,u);
for(i=k; i>=0; i--)
{
if(b[u][i] < 0) continue;
for(j=0; j+i+2<=k; j++)
{
if(b[v][j] < 0) continue;
b[u][i+j+2] = max(b[u][i+j+2], b[u][i]+b[v][j]);
}
}
}
for(p=head[u]; p!=-1; p=edge[p].next)
{
int v = edge[p].v;
if(v == fa) continue;
for(i=1; i<=k; i++) tt[i] = -1;
tt[0] = b[u][0];
for(int p1 = head[u]; p1!=-1; p1=edge[p1].next)
{
int v1 = edge[p1].v;
if(v1 == fa) continue;
if(v1 == v)
{
for(i=k; i>=0; i--)
{
if(tt[i] < 0) continue;
for(j=0; j+i+1<=k; j++)
{
maxt = max(g[v1][j], b[v1][j]);
if(maxt < 0) continue;
tt[i+j+1] = max(tt[i+j+1], tt[i]+maxt);
}
}
}
else
{
for(i=k; i>=0; i--)
{
if(tt[i] < 0) continue;
for(j=0; j+i+2<=k; j++)
{
if(b[v1][j] < 0) continue;
tt[i+j+2] = max(tt[i+j+2], tt[i]+b[v1][j]);
}
}
}
}
for(i=0; i<=k; i++)
{
if(tt[i] > g[u][i]) g[u][i] = tt[i];
}
}
}
int main()
{
int i;
int a,c;
while(~scanf("%d%d", &n, &k))
{
memset(g, -1, sizeof(g));
memset(b, -1, sizeof(b));
memset(head, -1, sizeof(head));
tot = 0;
for(i=1; i<=n; i++)
{
scanf("%d", &g[i][0]);
b[i][0] = g[i][0];
}
for(i=1; i<=n-1; i++)
{
scanf("%d%d", &a, &c);
add_edge(a,c);
add_edge(c,a);
}
dfs(1,-1);
int maxn = 0;
for(i=0; i<=k; i++)
{
maxn = max(maxn, max(g[1][i], b[1][i]));
}
printf("%d\n", maxn);
}
return 0;
}