算法分析:
这两道题目,选课是经典的树上背包问题,是分组背包的树上模型。积蓄程度是不定根树形dp问题。书上介绍地很详细。这里主要介绍下代码中的注意事项。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
int n, m, f[310][310], score[310];
struct node
{
int next, to;
}edg[310];
int h[310], cnt;
inline void add(int u, int v)
{
++cnt;
edg[cnt].next = h[u];
edg[cnt].to = v;
h[u] = cnt;
}
void dfs(int u)
{
f[u][1] = score[u];
for (int i = h[u]; i; i = edg[i].next)
{
int v = edg[i].to;
dfs(v);
for (int j = m; j >= 1; --j) // 逆序能够保证此时f[u][j-k]不包括v这颗子树
for (int k = 0; k < j; ++k)
f[u][j] = max(f[u][j], f[v][k] + f[u][j-k]);
}
}
int main()
{
scanf("%d%d", &n, &m);
int fa;
for (int i = 1; i <= n; ++i)
{
scanf("%d%d", &fa, &score[i]);
add(fa, i);
}
// 0号点是根结点
++m; // 多加了个0号点,选择数目加1
memset(f, 0xcf, sizeof(f));
dfs(0);
printf("%d\n", f[0][m]);
return 0;
}
反思与总结:
1.题目模型是森林,要构成树的话,需要额外增加结点,连向各棵树的根,这个额外结点为0号点。此时++m。
2.因为是从树根开始遍历,存图的时候,可以当做有向图存。
3.对于以下代码:
f
[
u
]
[
j
]
=
m
a
x
(
f
[
u
]
[
j
]
,
f
[
v
]
[
k
]
+
f
[
u
]
[
j
−
k
]
)
f[u][j] = max(f[u][j], f[v][k] + f[u][j-k])
f[u][j]=max(f[u][j],f[v][k]+f[u][j−k])
注意体会dfs的过程,当v这个分支遍历结束的时候,此时
f
[
v
]
[
k
]
f[v][k]
f[v][k]表示的是v这个分支中取体积为k的物品,
f
[
u
]
[
j
−
k
]
f[u][j-k]
f[u][j−k]表示的是已经遍历过的而不包含v这个分支的部分中体积为
j
−
k
j-k
j−k的物品。要保证
f
[
u
]
[
j
−
k
]
f[u][j-k]
f[u][j−k]不包含v这个分支,只能让m的那层循环逆序。类似01背包的逆序。
以下是积蓄程度的代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
int n, d[200010], f[200010], deg[200010];
struct node
{
int next, to, val;
}edg[400010];
int h[200010], cnt;
inline void add(int u, int v, int val)
{
++cnt;
edg[cnt].next = h[u];
edg[cnt].to = v;
edg[cnt].val = val;
h[u] = cnt;
}
void dfs1(int u, int fa)
{
d[u] = 0;
for (int i = h[u]; i; i = edg[i].next)
{
int v = edg[i].to;
if (v == fa) continue;
dfs1(v, u);
if (deg[v] == 1) d[u] += edg[i].val;
else d[u] = d[u] + min(edg[i].val, d[v]);
}
}
void dfs2(int u, int fa)
{
for (int i = h[u]; i; i = edg[i].next)
{
int v = edg[i].to;
if (v == fa) continue;
if (deg[u] == 1) f[v] = d[v] + edg[i].val;
else f[v] = d[v] + min(edg[i].val, f[u]-min(d[v], edg[i].val));
dfs2(v, u);
}
}
int main()
{
int T;
scanf("%d", &T);
while (T--)
{
scanf("%d", &n);
// 链式前向星的初始化,只要初始化cnt和h就可以了
cnt = 0;
memset(h, 0, sizeof(h));
memset(deg, 0, sizeof(deg));
int x, y, z;
for (int i = 1; i < n; ++i)
{
scanf("%d%d%d", &x, &y, &z);
add(x, y, z); add(y, x, z);
++deg[x]; ++deg[y];
}
dfs1(1, 0); // 计算d[x]
f[1] = d[1];
dfs2(1, 0); // 计算f[x]
int ans = 0;
for (int i = 1; i <= n; ++i) if (ans < f[i]) ans = f[i];
printf("%d\n", ans);
}
return 0;
}
反思与总结:
1.因为是多组数据,要打扫战场。对于链式前向星存图,想要初始化,只要初始化cnt和h就可以了。
2.应特别考虑叶子结点的情况,可以用一个数组deg表示每个结点的度,度为1的就是叶子结点。