题目传送门
这是一个通往Codeforces的门
这是一个通往vjudge的门
题目大意
问以节点1为根的大小为$n$的带标号二叉树有多少个满足最大匹配数为$k$。答案模$10^{9} + 7$。
树的最大匹配是指,选择尽量多的边,使得这些边没有公共点,最大匹配数是这个边集的大小。
考虑二叉树的最大匹配怎么做。
用$f[i]$表示$i$节点被覆盖的最大匹配数,$g[i]$表示$i$节点未被覆盖的最大匹配数。
那么有$f[i] = \max (\max(f[l], g[l]) + f[r], \max (f[r], g[r]) + f[l]) + 1$,$g[i] = \max (f[l], g[l]) + \max (f[r], g[r])$。
然后树形dp即可。
因此可以设计出这里的状态$f[i][j][k]$表示$i$个点的满足条件二叉树,选根的最大匹配数为$j$,不选根的最大匹配数为$k$。
但是这么做时间会炸掉。
必须考虑优化状态。通过打表找规律其实是看了题解,可以发现一个神奇的事情。
神奇的性质 一棵二叉树,如果节点数大于1,那么根被覆盖要么比根未被覆盖的情况的最大匹配数多1要么和它相等。
证明 要证明它,等价于证明$g[i] \geqslant f[i] - 1$以及$f[i] \geqslant g[i]$。
首先考虑前一个不等式,对于一个根被覆盖的匹配,我只需去掉根和某个子节点的匹配,然后就变成了一个根未被覆盖的匹配,但是匹配数只比原来少一,所以$g[i] \geqslant f[i] - 1$。
然后考虑后一个不等式,讨论$g[i]$对应的匹配中根节点的子树,因为节点数大于1,所以根至少存在一个子树。
- 子树的根节点在匹配中。那么断开匹配它的边,让它和根节点之间的边被匹配。
- 子数的根节点不在匹配中。那么让它和根节点质检的边匹配。
这样就证明了$f[i] \geqslant g[i]$。
因此,定理得证。
因此,可以直接去掉$k$,把它变成$0 / 1$,表示根被覆盖的最大匹配数是否比根未被覆盖的最大匹配数多1。
然后再来考虑一个问题:如何处理算重的情况?
- 钦定左子树大小小于等于右子树
- 当左子树大小等于右子树的时候,钦定2号点在左子树内。
以上很多讨论都要求节点数大于2,所以节点数小于等于1的情况直接赋初值。
然后考虑转移。
枚举左子树大小,左右子树匹配数,转移的时候特判左子树是否为空,考虑从左右子树中重新选取一个点作为新根,以及左右子树各有哪些点。
具体转移请看代码。
Code
1 /** 2 * Codeforces 3 * Problem#382E 4 * Accepted 5 * Time: 16ms 6 * Memory: 2056k 7 */ 8 #include <bits/stdc++.h> 9 using namespace std; 10 typedef bool boolean; 11 12 const int N = 55, M = 1e9 + 7; 13 14 int n, k; 15 int C[N][N]; 16 int f[N][N][2]; 17 18 inline void init() { 19 scanf("%d%d", &n, &k); 20 } 21 22 inline void solve() { 23 C[0][0] = 1; 24 for (int i = 1; i <= n; i++) { 25 C[i][0] = C[i][i] = 1; 26 for (int j = 1; j < i; j++) 27 C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % M; 28 } 29 30 f[0][0][0] = 1, f[1][0][0] = 1; 31 for (int i = 2; i <= n; i++) { 32 for (int ls = 0, rs, c, f0, f1, cl; (ls << 1) < i; ls++) { 33 rs = i - 1 - ls; 34 c = ((ls == rs) ? (C[i - 2][ls - 1]) : (C[i - 1][ls])); 35 cl = ((!ls) ? (1) : (ls)); 36 for (int lk = 0; (lk << 1) <= ls; lk++) { 37 for (int rk = 0; (rk << 1) <= rs; rk++) { 38 for (int j = 0, b1, b2, b; j < 4; j++) { 39 b1 = (j & 1), b2 = ((j & 2) >> 1); 40 if (!lk && b1) continue; 41 if (!rk && b2) continue; 42 f0 = lk + rk, f1 = (ls) ? (lk + rk - min(b1, b2) + 1) : (rk - b2 + 1); 43 b = f1 - f0; 44 f[i][f1][b] = (f[i][f1][b] + (f[ls][lk][b1] * 1ll * f[rs][rk][b2] % M * c) % M * cl * rs % M) % M; 45 } 46 } 47 } 48 } 49 } 50 printf("%d\n", (f[n][k][0] + f[n][k][1]) % M); 51 } 52 53 int main() { 54 init(); 55 solve(); 56 return 0; 57 }