The Hanged Man
题解
树dp板子题。
首先 O ( n m 2 ) O\left(nm^2\right) O(nm2)的朴素dp做法应该是十分容易想到的,直接合并即可。
由于MAX卷积什么的太,太神仙了,我都没听说过,所以显然不能用来优化转移过程降低时间复杂度,我们就考虑改变一下dp状态。
首先,我们知道如果每次只合并一个数的话每次转移明显就是 O ( m ) O(m) O(m)的了,而对于这个数我们又只需要考虑它的父亲在不在,所以很快就可以想到状压,合并时看它父亲在不在。
但很明显,直接状压是不现实的, n ⩽ 50 n\leqslant 50 n⩽50。在合并的过程中许多状态都是不必要的,或者说是可以合并在一起的,所以我们就想到了重链剖分。
对于重链剖分后的状态,我们可以从叶子节点逐渐向上加上去。
如果我们当前的合并到节点 u u u,我们就需要将节点 u u u到根节点的所有重链上最后一个点加到状态上去,记录这些点是否被选择。
我们可以先对其重儿子跑完dp,跑完重儿子后就将重儿子的dp数组合到点 u u u上,再将点 u u u加入dp。将 u u u在状态中的位置赋值为其重儿子在其状态中的位置后再依次加入加入它的儿子节点。
每次跑完儿子节点后需要完成一个去掉儿子节点状态的操作,将 d p dp dp值合并,由于儿子节点是否被选取对于其它的儿子来说是没有意义的。
而每次跑完重儿子后加入点 u u u时也需要先去掉其重儿子节点的状态,加入一个节点时只有它的父亲与重儿子可能被加入,判断这两者是否被选即可。
时间复杂度 O ( n 2 m ) O\left(n^2m\right) O(n2m),听说还有种点分树的做法,不过我确实没写出来。
源码
#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 5005
typedef long long LL;
const LL INF=0x7f7f7f7f7f7f;
template<typename _T>
void read(_T