Skyh 学长来给我们讲课..主要是树的较朴素问题..

树形Dp

选课

一道经典的树形Dp,可以使用树形背包..(想当初自己磨的时候磨出了一个一千五百毫秒的解法..结果发现五十毫秒就能A..)

这里详细讲解了有关于背包从 \(O(nm\)2\()\) 优化到 \(O(nm)\) 的方法,并且作了详细讲解..

原始状态转移方程:

\[for (int\ i = m;i \geq 0; --i) \]

\[for (int\ j = m - i; j\geq 0; --j) \]

\[f[x][i + j] = max(f[x][i + j], f[x][i] + f[y][j]); \]

实际状态转移方程: ( p 为当前 u 的 size 大小 )

\[for (int\ i = min(p, m + 1);\ i\ ; i--) \]

\[for (int\ j = 1; j <= siz\ and\ i + j <= m + 1; j++) \]

\[f[u][i + j] = max(f[u][i + j], f[u][i] + f[v][j]); \]

对于每次转移,我们都会将子树中的信息全部转移到以 u 为根的树中,同时将子树大小合并给 u 。但是我们会发现,我们每次都只需要转移小于等于 m 的信息,所以这时我们选择对其进行删减,这时复杂度看起来仍然是 \(O(nm\)2\()\) 的,但事实上已经发生了变化..

令 p[u] 表示当前以 u 为根的树的大小,即已经合并的 size;siz[v] 为即将合并的子树大小

<1> 对于 \(p[u]<m\ and\ siz[v]<m\)

我们每次转移,都相当于让已经合并的节点去和即将合并的节点在lca处去一一匹配,可以理解为每个节点都要和另外的每个节点进行匹配,所以相当于是 \(O(n\)2\()\);

<2> 对于 \(p[u]>m\ and\ siz[v]<m\)

我们可以理解为,在大小为m的限制下,有些节点并不能和其它节点进行一一匹配,也就是变成了上面情况的一部分删减..

<3> 对于 \(siz[v]\geq m\)

对于每个siz超过 \(m\) 的子树,这样的子树一定不会超过 \(\dfrac{n}{m}\),那么就可以每次转移了 \(m^2\),但是这样的转移只发生了 \(\dfrac{n}{m}\) 次,所以复杂度就是 \(O(\)\(m\)2*\(\dfrac{n}{m}\)\()\) ..

[HAOI2015]树上染色

这道题使用了一个比较妙的方法:即是将两两之间的点距转化为了每条边所被经过的次数,这样之后,我们再进行计数与统计便更加直观了..

我们可以记录每个子树内的黑点个数,这样我们就有了每个子树内的白点个数,然后我们就有了子树外的黑白点个数,这样这条边被经过的次数也就有了..

CSP-S 树

这是一道树上的期望,一开始想的是可以记录每个点进入子树后再次回到自己的期望步数(目前也仍然觉得很可行,当然我现在依旧没码..),然后听学长讲的是记录每个点到达父亲的期望步数..
树形结构学习笔记_树形dp

Outer space invaders

学长讲题之前给了一段时间思考,发现这是个序列,于是想到可以优化序列的东西,然后就想到了线段树,又发现了一个显然的性质,无论如何最远的那个Alien都是要被抹杀的,所以一定要让这个昂贵的话费作出的贡献尽量大。
但是自己却是想到了贪心的算法,就是记录当前外星人被消灭的最早需求时间,于是在现在时间即将大于最早需求的时候将Alien抹杀,但是我们可以发现这是错误的,与刚刚上面发现的那个性质相悖,如果一个距离极其小的外星人A,ta 被抹杀的需求时间\(t_1\) 也极其早,但是于此同时有一个极其远的外星人B,ta 出现的时间 \(t_2\) 早于 \(t_1\) ,但是可以在很久之后再被抹杀,所以这个时候就不能提早抹杀这个外星人,因为ta 的距离很大,可以作出更大的贡献..
正解是区间dp,关于为什么学长要在树形dp的专题里面放下这样一个题目..那是因为这个题目有一点点类似于笛卡尔树..
不过这道题突然给了我一个启发:就是这个武器的喷头既然是发散的,那么如果别的题出现了范围内的范围性解决,那么可以将这个范围看成一条线----------------------------------------------------------------------------------------------------
我们可以设\(dp[i][j]\)为解决 区间\([i,j]\) 之间所有Alien 的代价,然后再枚举 \(i\sim j\) 之间以哪个点为转移点就可以了..

有关树的重心

CSP-S 树的重心

一道有关重心的题目,重心性质的详情,请看这里

我们可以先求出每棵子树的重心,做法:
\(son[now]\)为重儿子,我们已知某棵子树的重心在\(path[u,v]\)的这一整条路径上,所以我们每次记录重儿子的重心,向上枚举即可,这是\(O(n)\)的做法;而还有一种\(O(nlogn)\)的做法,就是我们预处理出倍增数组,然后从重儿子的重心向上倍增即可..于是我们便求出了割边后子树内的重心..

对于子树外的重心,这里只介绍\(O(n)\)的做法,我们分两种情况讨论:
首先我们将重心设置为\(root\)

  1. 删掉非重儿子上的子树,那么树的重心在重链上向下移动.

我们可以发现,随着删掉子树大小的增大,向下移动的深度越多,由此可见其单调性,于是\(O(n)\)扫描重链即可.

  1. 删掉重儿子上的子树,那么树的重心会在次重链上向下移动.

这是显然的。同样遵循单调性,\(O(n)\)扫描同上..