题意:有一颗由长度为2 * n的合法的括号序列构成的字典树,现在你需要在这颗字典树上选择一些不连接的边,问最多可以选择多少条边?
思路:不考虑题目条件的话,我们只考虑在随意的一棵树上选择边,这是一个贪心的问题,只需要每次选择叶子结点和它的父亲这条边,然后把它和它父亲节点删除就可以了,不断进行这个操作,就可以得到最终的答案。但是题目中的这颗字典树的规模是非常大的,不能用正常的方法。我们发现这颗字典树有2个性质:
1:这颗字典树的所有叶子结点都在同一层。
2:有很多的子树是重复的。
通过第一个性质以及贪心算法,我们知道答案相当于是计算奇数层的点的个数(如果根按第0层开始算),从第2n层开始选择,因为2n层节点个数一定大于等于第2n - 1层,所以可以选择的最多的边数是2n - 1层的节点数,2n - 2层同理。
那么问题就转化为的求这颗字典树的奇数层的节点的个数了。
现在我们需要用到第二个性质,在这颗字典树中,容易发现,有些树的转移是相同的,我们假设用i(当前填充的括号数)和j(平衡因子)来表示一个状态,那么它只可能转移到dp[i + 1][j + 1]或者dp[i + 1][j - 1], 对应的转移是加一个右括号和加一个左括号,而i同时还等于深度,所以我可以用dp[i][j]表示深度为i,平衡因子是j的节点的个数,这样就可以O(n ^ 2)转移了。
代码:
#include <bits/stdc++.h> #define LL long long using namespace std; const LL mod = 1000000007; const int maxn = 2010; int dp[maxn][maxn]; int main() { int n; scanf("%d", &n); dp[0][0] = 1; LL ans = 0; for (int i = 1; i <= 2 * n; i++) { for (int j = 0; j <= i; j++) { if(j > 0) dp[i][j] = (dp[i][j] + dp[i - 1][j - 1]) % mod; dp[i][j] = (dp[i][j] + dp[i - 1][j + 1]) % mod; if(i % 2 == 1 && (i - j) % 2 == 0 && 2 * n - i >= j) ans = (ans + dp[i][j]) % mod; } } printf("%lld ", ans); }