codeforce 1152 (round 554) DIV2:D. Neko and Aki's Prank (思维 + dp + 贪心)

题意:有n对括号,把这n对括号所有合法的匹配画在字典树上,字典树的每一个结点都是合法的括号匹配(即任意一个前缀左括号数量大于等于右括号),问这棵字典树上不相交边集的最大数量是多少?

解法:明白题意和字典树概念后,似乎可以用树形dp搞,建树是不可能建的,一看模数1e9 + 7,那么只能凭空想象然后在虚树上进行dp了,手画了一下第三个样例的字典树,有21个结点,贪心的从叶子结点开始选连接到它的边,一层一层往上选,然后扣去有分支结点的情况,验证了一下发现可以。
具体过程:用dp求出所有合法的括号序列情况的个数,dp[i][j]代表前i个括号放j个左括号的合法情况。这样这棵树的最后一层的结点个数是dp[2 * n][n],上一层结点个数 为 2 * n - 1个点的所有合法情况之和,这一层对答案的贡献:先选出所有叶子结点,再扣去上一层有分支的结点(因为最多只分两支,只要减去一次就行),什么结点可能有分支呢?模拟一下决策过程,一个结点既可以放左括号又可以放右括号,当且仅当该结点的左括号数量一定大于右括号数量,且左括号数量小于n。求出上一层所有这样的结点的个数,扣去即可。因为边不能相交,每次要往上跳两层,可以发现对答案有贡献的层数都是偶数层,每一层对答案有贡献的结点个数是:例如第i层,是sum(dp[i][i / 2] -> dp[i][n])。最后一层是特殊情况,直接就是dp[2 * n][n]。要扣去的层数都是奇数层,要扣去的数量为sum(dp[i][i / 2 + 1] -> dp[i][n - 1]),这些都是左括号比右括号多且没放够n个左括号的情况,然后不停的往上搞,一路搞到根,一共有2000层,但只需要想上跳1000次,复杂度是o(n ^ 2)

代码又很短:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e3 + 10;
const int mod = 1e9 + 7;
long long dp[maxn * 3][maxn * 3];
int n;
int main() {
	scanf("%d",&n);
	dp[1][1] = 1;
	long long res = 0;
	for(int i = 1; i <= 2 * n; i++) {
		for(int j = 1; j <= 2 * n; j++) {
			if(2 * j < i) continue;
			if(j > n) continue; 
			dp[i][j] += dp[i - 1][j] % mod;
			dp[i][j] += dp[i - 1][j - 1] % mod;
		}
	}
	for(int i = 2 * n; i >= 1; i -= 2) {
		for(int j = 1; j <= n; j++)
			res = (res + dp[i][j]) % mod;
		for(int j = (i - 1) / 2 + 1; j < n; j++)
			res = (res + mod - dp[i - 1][j]) % mod;
	}
	printf("%lld\n",res % mod);
	return 0;
}

PS(思路历程):题意看了挺久,单词没看懂,看着字典树想起了一些合法括号匹配的性质,以及字典树的性质。
不相交边集数量,等同于二分图的最大匹配边,想起了匈牙利树,在匈牙利树上做增广?(没搞过),一看n只有1000,似乎可行,又想起来可以直接在字典树上树形dp,每条边分选和不选,也可以得出答案。但题目说答案可能很大,要mod 1e9 + 7,模数这么大说明边数很多,树可能建不出来。难道要在虚数上进行树形dp吗?不过可以先求出合法结点个数再说(次处忘了开long long 和取模,爆了,然后没发现,因为最后答案还是一副合理的样子),求出合法结点个数后,对着画出的样例3的字典树有点想法,似乎我的dp的状态可以表示某一层的结点个数?在样例三的字典树上模拟出了贪心得到答案的过程,从叶子结点层开始,能选的边都选上,(全部的边扣掉上一层分支结点的个数),然后不停的往上跳两层,重复选边的过程,每一层的边数就是那层的点树,而那层的点树可以用已经求得的dp计算得到。思考了一下如何得到每一层结点个数,改了一下取模,开了long long,A了。

猜你喜欢

转载自blog.csdn.net/qq_41997978/article/details/89575281