这是我参与11月更文挑战的第10天,活动详情查看:2021最后一次更文挑战.
洛谷P2014 选课
给定门课的学分与先修课程(一个或零个),问选个课程的最大学分。
认为没有先修课程的课,以0为先修课程,且0课程的学分为0,以此构建一棵树。
表示以为根的子树中选修门可以获得的最大学分。
答案就是.
注意:树上的依赖关系体现在dp[i][0]=-inf,即不能父节点不选。
复杂度优化
单次背包合并的复杂度是,一共需要合并次,总复杂度.
下面的代码里已经添加了一个常数优化:当前背包容量cnt,也就是子树大小。
vector<int> son[M];
int dp[M][M];
int dfs(int u)
{
int cnt = 1;
for(auto v:son[u])
{
cnt += dfs(v);
for(int j=cnt; j>=1; --j)
for(int k=0; k<=j; ++k)
dp[u][j] = max(dp[u][j], dp[u][k] + dp[v][j-k]);
}
return cnt;
}
上下界优化一共包含3个优化,都是舍弃无用状态
- 的状态是无用的,因为答案不需要(在此题是m+1)
- ,的状态是无用的,即上面代码里的优化
- 的状态是无用的,其中表示以子树的节点数
综上,代码可以改进为:
vector<int> son[M];
int dp[M][M];
int dfs(int u)
{
int cnt = 1;
for(auto v:son[u])
{
int cntv = dfs(v);
for(int j=min(m+1,cnt+cntv); j>=1; --j)
for(int k=max(0,j-cnt), rk=min(cntv,j); k<=rk; ++k)
dp[u][j] = max(dp[u][j], dp[u][j-k] + dp[v][k]);
cnt += cntv;
}
return cnt;
}
这份代码里一共多了三个界限:
- 的上限是,对应于上述的优化1、2
- 的下限是,即,保证了。
- 的上限是,对应上述的优化3.
需要注意的是,这里固定了的下标是,的下标是,不能再交换
复杂度证明
(1)每个点对只会在它们的处被合并一次,所以复杂度
(2)来自czq爷的势能分析,
- 单次合并的复杂度是
- 设获得一棵节点数为的子树的代价是,
- 那么获得的复杂度正好是
(3)因为枚举过程中还对取min,所以实际复杂度是.
详细证明见www.cnblogs.com/ouuan/p/Bac…
此文章也发表于我的 CSDN 博客中。