题目大意
学生要在n门课程中选m门课程,每门课程最多依赖于一门前置课程(可以有多门课程没有前置课程),问能获得的最大学分是多少。(1<=m<=n<300)
问题分析
- 显然,每门课程依赖至多一门,有课程可以没有依赖,所以这是一个多叉树森林。
- 森林不太好处理,所以我们可以给森林加个虚拟根节点#0,让每棵树的根节点连到这个总结点上。问题即转化为在这个多叉树#0上选m门课程的最大学分,自然而然地,我们得到了DP状态 d p i , j dp_{i, j} dpi,j,即在i子树上选择j门课程可以得到的最大学分。
- 按照二叉树的思维来想,i子树的j门课程可以分配给它的子树们,即
d p [ i ] [ j ] = ∑ d p [ s o n i ] [ k i ] + p n t [ i ] , w h e r e ∑ k i = j − 1 dp[i][j] = \sum dp[son_i][k_i] + pnt[i]\ , \ where \ \sum k_i = j - 1 dp[i][j]=∑dp[soni][ki]+pnt[i] , where ∑ki=j−1 - 但显然,由于i子树可能有很多儿子,这样排列组合下来复杂度很高,所以这样不可取。当然,多叉树直接算的办法还是有的,但我目前没有完全理解(
确实不好理解)。事实上,我们可以曲线救国。既然二叉树的办法套在多叉树上不好用,那我们就干脆把多叉树变成二叉树——孩子兄弟树。 - 孩子兄弟树,即左子树是孩子,右子树是兄弟。显然,选i子树的孩子课程就得选父亲课程,但是选兄弟课程就不会受i子树的影响,因此DP状态
d
p
i
,
j
dp_{i, j}
dpi,j,即在i子树(i和i的孩子、i的兄弟)上选择j门课程可以得到的最大学分,转移式为
d p [ i ] [ j ] = { d p [ b r o i ] [ j ] m a x { d p [ s o n i ] [ k ] + d p [ b r o i ] [ j − k − 1 ] + p n t [ i ] } dp[i][j] = \begin{cases} dp[bro_i][j]\\ max \{dp[son_i][k] + dp[bro_i][j - k - 1] + pnt[i] \} \end{cases} dp[i][j]={dp[broi][j]max{dp[soni][k]+dp[broi][j−k−1]+pnt[i]} - 对应上式的情况分别为
- 不选课程i,只选i兄弟的课程
- 选i课程, 再选 i的孩子课程和兄弟课程共计j-1门
- 此处采取了DFS的方式保证了遍历顺序,准确的讲是记忆化搜索。
AC代码
# include <cstdio>
# include <algorithm>
# include <cstring>
# include <cstdlib>
using namespace std;
const int maxn = 310;
int fa[maxn], son[maxn], bro[maxn], val[maxn];//fa[]是不必要的
int dp[maxn][maxn];
inline void add(int f, int s){bro[s] = son[f], son[f] = s, fa[s] = f;}
int dfs(int i, int j)
{
if(i == -1)return 0;//走到空位置
if(j == 0)return 0;//剩余选择数为0门
if(dp[i][j] != -1)return dp[i][j];
int temp = -1 << 30;
temp = max(temp, dfs(bro[i], j));
for(int k = 0; k <= (j - 1); k++)
temp = max(temp, dfs(son[i], k) + dfs(bro[i], j - k - 1) + val[i]);
return dp[i][j] = temp;
}
int main()
{
memset(dp, -1, sizeof(dp));
memset(son, -1, sizeof(son));
memset(bro, -1, sizeof(bro));
int n, m;
scanf("%d%d", &n, &m);
for(int s = 1; s <= n ; s++)
{
int f,v;
scanf("%d%d", &f, &v);
add(f, s); val[s] = v;
}
printf("%d\n", dfs(0, m + 1));
system("pause");
return 0;
}
参考博客
- https://www.luogu.org/blog/user15011/solution-p2014 (多叉树转二叉树,大赞!)
- https://www.luogu.org/blog/105496/solution-p2014 (分析了两种方法,多叉/二叉)
- https://www.luogu.org/blog/user25845/solution-p2014
(多叉树法,分析得不错,但多叉有个地方我理解不了~)