\(\text{Ju Lao}\)
博主在网上寻找树形背包的复杂度证明时千辛万苦,每次找到一篇博客又觉得有点怪怪的,似乎有点问题。
但有那么一个 \(\text{Ju Lao}\),如漫漫长夜的一盏明灯,照亮了我前方的迷途,使我获得了前进的力量!!!
\(\text{Description}\)
有一种树形背包,第一维表示点,第二维为 \(size\) 且限制不超过 \(m\),这种背包的时间复杂度是 \(\mathcal O(n\times m)\) 的。
void dfs(int u,int last) {
sz[u]=1;
// initialize dp[u]
for(int k=head[u];k;k=e[k].next)
{
int v=e[k].to; if(v==last) continue;
dfs(v,u);
for(int j=0;j<=min(m,sz[u]);++j)
for(int k=0;k<=min(m-j,sz[v]);++k)
// dp[u][j] * dp[v][k] -> dp[u][j+k]
}
} // 直接放的 Ju Lao 模板
\(\text{Pre Cheese}\)
我 \(\text{Chinglish}\) 一直可以。
- 极小大子树:它本身的大小 \(\ge m\),但它所有子树的大小都 \(<m\)。
\(\text{Proof}\)
第一次算这玩意儿复杂度时:这不是显然 \(\mathcal{O(n^3)}\) ?这不是有手就行?
咳咳咳这是一个身残志坚的励志故事。
我们假设 \(v\) 是 \(u\) 的儿子,现在对这两个进行合并。其中 \(size\) 是已经合并的子树总大小,\(siz[x]\) 是根节点为 \(x\) 的子树大小。
-
\(size< m,siz[v]< m\)。
这相当于没有限制。我们发现一对点只会在他们的 \(lca\) 为 \(u\) 时才会匹配,越过 \(lca\) 就已经在一棵子树里了。时间复杂度 \(\mathcal O(n^2)\)。
-
\(size\ge m,siz[v]\ge m\)。
所有的极小大子树都不会相交,所以最多有 \(\frac{n}{m}\) 个。这种情况的出现,其实就是 \(u\) 已经合并的子树中有极小大子树(注意不是指 \(u\) 的单个子树是极小大子树,而是合并途中出现这样的树),\(v\) 中有极小大子树。我们知道,两棵子树合并之后就是一个整体,不会再被裂开了。那么其实这种情况的合并次数应该是极小大子树个数 \(-1\)。单次合并时间复杂度 \(\mathcal O(m^2)\),总时间复杂度 \(\mathcal O(m^2\times \frac{n}{m})=\mathcal O(n\times m)\)。
-
\(size\ge m,siz[v]< m\)。
总时间复杂度 \(\mathcal O(m\times \sum siz[v])\)。考虑出现了这样一种情况,那么容易发现,\(v\) 的子树内部不可能发生这种类型的合并,这样的合并只可能发生在外面,如果外面又发生了这种情况,那么其对应的 \(v'\) 内部也不可能发生这种类型的合并…每一个被记入的 \(siz[v]\) 都不会被重新加进 \(\text{sum}\) 了。所以可以将 \(\sum siz[v]\) 看作 \(n\)(实际肯定比 \(n\) 小)。总时间复杂度 \(\mathcal O(m\times n)\)。
-
\(size< m,siz[v]\ge m\)。
同理可证。
\(\text{Warning}\)
- 我们证明了上述背包的时间复杂度,但有些树形背包写法是会 \(\text{T}\) 的,所以一定要按上述枚举才是正确时间复杂度。