Codeforces 1152D DP

题意:有一颗由长度为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\n", ans);
}

  

posted @ 2019-05-08 09:20  维和战艇机  阅读(209)  评论(0编辑  收藏  举报