题目大意
键盘上有左括号(,右括号),和退格键-,共三个键。
牛牛希望按键n次,使得输入的字符串恰好一个合法的括号序列。
每按一次左括号(,字符串末尾追加一个左括号(
每按一次右括号),字符串末尾追加一个右括号)
每按一次退格键-,会删掉字符串的最后一个字符,
特别的,如果字符串为空,牛牛也可以按退格,但是什么都不会发生。
合法括号序列的定义和上一场比赛中的C题是一样的
https://www.nowcoder.com/acm/contest/164/C
输出方案数对p取模,注意p可能不是质数。
注:只要按键方法不同,就是不同的方案,即使得到的序列一样。
此题有一个比较麻烦的地方就是需要把删除的贡献也考虑在内
不妨将当前括号序列长度考虑在内
设 g[i][j] 表示进行了 i 次操作后序列长度为 j 的方案数
转移也很好想 : g[i][j] = g[i -1][j - 1] + g[i - 1][j + 1] * 2
乘以 2 就是填入左 / 右括号,j = 0 时把 j - 1 改成 0
由于统计答案时统计的是合法括号序,就再 dp 出长度为 i 时的合法括号序即可
状态设计为 f[i][j] 表示当前长度为 i ,还有 j 个左括号未匹配
代码:
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cctype>
#include<cstdio>
using namespace std;
typedef long long ll;
const int MAXN = 1005;
int n;
ll f[MAXN][MAXN], g[MAXN][MAXN];
ll p, ans;
int main() {
scanf("%d%lld", &n, &p);
f[0][0] = 1ll;
for(int i = 1; i <= n; ++i) {
f[i][0] = f[i - 1][1];
for(int j = 1; j <= i; ++j) {
f[i][j] = (f[i][j] + f[i - 1][j + 1] + f[i - 1][j - 1]) % p;
}
}
g[0][0] = 1ll;
for(int i = 1; i <= n; ++i) {
for(int j = 0; j <= i; ++j) {
g[i][j] = (g[i - 1][max(j - 1, 0)] + g[i - 1][j + 1] * 2) % p;
}
}
ans = g[n][0];
for(int j = 2; j <= n; j += 2) {
ans = (ans + f[j][0] * g[n][j]) % p;
}
printf("%lld\n", ans);
return 0;
}
因为合法的括号序长度一定是偶数,所以枚举长度也枚举偶数