【笔记】树上背包的复杂度分析

214 阅读2分钟

这是我参与11月更文挑战的第10天,活动详情查看:2021最后一次更文挑战.


参考 树上背包的上下界优化 ouuan


洛谷P2014 选课

给定n(300)n(300)门课的学分与先修课程(一个或零个),问选mm个课程的最大学分。

认为没有先修课程的课,以0为先修课程,且0课程的学分为0,以此构建一棵树。

dp[i][j]dp[i][j]表示以ii为根的子树中选修jj门可以获得的最大学分。

dp[i][j]=max{dp[v][j]+dp[i][jk]}dp[i][j] = max\{dp[v][j]+dp[i][j-k]\}

dp[i][1]=i门课的学分,dp[i][0]=infdp[i][1] = 第i门课的学分,dp[i][0]=-inf

答案就是dp[0][m+1]dp[0][m+1].

注意:树上的依赖关系体现在dp[i][0]=-inf,即不能父节点不选。


复杂度优化

单次背包合并的复杂度是O(n2)O(n^2),一共需要合并O(n)O(n)次,总复杂度O(n3)O(n^3).

下面的代码里已经添加了一个常数优化:当前背包容量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个优化,都是舍弃无用状态

  1. dp[u][j]j>mdp[u][j],j>m的状态是无用的,因为答案不需要(在此题是m+1)
  2. dp[u][j]j>当前背包容量dp[u][j],j>当前背包容量,的状态是无用的,即上面代码里的优化
  3. dp[v][k]k>size[v]dp[v][k],k>size[v]的状态是无用的,其中size[v]size[v]表示以vv子树的节点数

综上,代码可以改进为:

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. jj的上限是min(m+1,cnt+cv)min(m+1,cnt+cv),对应于上述的优化1、2
  2. kk的下限是max(0,jcnt)max(0,j-cnt),即kjcntk\geq j-cnt,保证了jkcntj-k\leq cnt
  3. kk的上限是min(cntv,j)min(cntv,j),对应上述的优化3.

需要注意的是,这里固定了dp[v]dp[v]的下标是kkdp[u]dp[u]的下标是nkn-k,不能再交换

复杂度证明

(1)每个点对只会在它们的lcalca处被合并一次,所以复杂度O(n2)O(n^2)

(2)来自czq爷的势能分析,

  1. 单次合并的复杂度是O(szuszv)O(sz_usz_v)
  2. 设获得一棵节点数为ss的子树的代价是1/2s21/2s^2
  3. 那么获得(szu+szv)(sz_u+sz_v)的复杂度正好是1/2(szu+szv)2=0.5szu2+0.5szv2+szuszv1/2(sz_u+sz_v)^2=0.5sz_u^2+0.5sz_v^2+sz_usz_v

(3)因为枚举过程中还对mm取min,所以实际复杂度是O(nm)O(nm).

详细证明见www.cnblogs.com/ouuan/p/Bac…


此文章也发表于我的 CSDN 博客中。