2021信奥赛C++提高组csp-s复赛真题及题解:括号序列

题目描述
小 w 在赛场上遇到了这样一个题:一个长度为 n n n 且符合规范的括号序列,其有些位置已经确定了,有些位置尚未确定,求这样的括号序列一共有多少个。
身经百战的小 w 当然一眼就秒了这题,不仅如此,他还觉得一场正式比赛出这么简单的模板题也太小儿科了,于是他把这题进行了加强之后顺手扔给了小 c。
具体而言,小 w 定义"超级括号序列"是由字符 (、)、* 组成的字符串,并且对于某个给定的常数 k k k,给出了"符合规范的超级括号序列"的定义如下:
()、(S)均是符合规范的超级括号序列,其中S表示任意一个仅由不超过 k个字符*组成的非空字符串(以下两条规则中的S均为此含义);- 如果字符串
A和B均为符合规范的超级括号序列,那么字符串AB、ASB均为符合规范的超级括号序列,其中AB表示把字符串A和字符串B拼接在一起形成的字符串; - 如果字符串
A为符合规范的超级括号序列,那么字符串(A)、(SA)、(AS)均为符合规范的超级括号序列。 - 所有符合规范的超级括号序列均可通过上述 3 条规则得到。
例如,若 k = 3 k = 3 k=3,则字符串 ((**()*(*))*)(***) 是符合规范的超级括号序列,但字符串 *()、(*()*)、((**))*)、(****(*)) 均不是。特别地,空字符串也不被视为符合规范的超级括号序列。
现在给出一个长度为 n n n 的超级括号序列,其中有一些位置的字符已经确定,另外一些位置的字符尚未确定(用 ? 表示)。小 w 希望能计算出:有多少种将所有尚未确定的字符一一确定的方法,使得得到的字符串是一个符合规范的超级括号序列?
可怜的小 c 并不会做这道题,于是只好请求你来帮忙。
输入格式
第一行,两个正整数 n , k n, k n,k。
第二行,一个长度为 n n n 且仅由 (、)、*、? 构成的字符串 S S S。
输出格式
输出一个非负整数表示答案对 10 9 + 7 {10}^9 + 7 109+7 取模的结果。
输入输出样例 1
输入 1
7 3
(*??*??
输出 1
5
输入输出样例 2
输入 2
10 2
???(*??(?)
输出 2
19
说明/提示
【样例解释 #1】
如下几种方案是符合规范的:
plain
(**)*()
(**(*))
(*(**))
(*)**()
(*)(**)
【数据范围】
| 测试点编号 | n ≤ n \le n≤ | 特殊性质 |
|---|---|---|
| 1 ∼ 3 1 \sim 3 1∼3 | 15 15 15 | 无 |
| 4 ∼ 8 4 \sim 8 4∼8 | 40 40 40 | 无 |
| 9 ∼ 13 9 \sim 13 9∼13 | 100 100 100 | 无 |
| 14 ∼ 15 14 \sim 15 14∼15 | 500 500 500 | S S S 串中仅含有字符 ? |
| 16 ∼ 20 16 \sim 20 16∼20 | 500 500 500 | 无 |
对于 100 % 100 \% 100% 的数据, 1 ≤ k ≤ n ≤ 500 1 \le k \le n \le 500 1≤k≤n≤500。
思路分析
状态定义
定义 dp[l][r][t] 表示区间 [l, r] 在状态 t 下的方案数,其中 t 有6种状态:
dp[l][r][0]: 区间[l, r]全是*且长度 ≤ kdp[l][r][1]: 区间[l, r]是(A)形式的合法序列(A可以是多种状态)dp[l][r][2]: 区间[l, r]是A*B形式,且以*结尾dp[l][r][3]: 区间[l, r]是完整的合法序列(最终答案状态)dp[l][r][4]: 区间[l, r]是(A*B)形式,且以)结尾dp[l][r][5]: 区间[l, r]是A*形式,且以*结尾
转移方程
-
状态0 :全是
*且长度 ≤ kdp[l][r][0] = dp[l][r-1][0] && (s[r]=='*'||s[r]=='?')
-
状态1 :
(A)形式- 要求
s[l]=='('且s[r]==')' dp[l][r][1] = dp[l+1][r-1][0] + dp[l+1][r-1][2] + dp[l+1][r-1][3] + dp[l+1][r-1][4]
- 要求
-
状态2 :
A*B形式,以*结尾- 枚举分割点
i,要求[i+1, r]是状态0 dp[l][r][2] += dp[l][i][3] * dp[i+1][r][0]
- 枚举分割点
-
状态3:完整合法序列
- 从状态1直接转移:
dp[l][r][3] += dp[l][r][1] - 从拼接转移:
dp[l][r][3] += (dp[l][i][2] + dp[l][i][3]) * dp[i+1][r][1]
- 从状态1直接转移:
-
状态4 :
(A*B)形式,以)结尾dp[l][r][4] += (dp[l][i][4] + dp[l][i][5]) * dp[i+1][r][1]
-
状态5 :
A*形式,以*结尾- 从状态0直接转移:
dp[l][r][5] += dp[l][r][0] - 从拼接转移:
dp[l][r][5] += dp[l][i][4] * dp[i+1][r][0]
- 从状态0直接转移:
初始化
dp[i][i-1][0] = 1:处理空串
代码实现
cpp
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 505;
const int mod = 1e9 + 7;
int n, k;
char s[N];
ll dp[N][N][6];
// 判断字符是否匹配括号
bool check(char a, char b) {
return (a == '(' || a == '?') && (b == ')' || b == '?');
}
int main() {
scanf("%d%d", &n, &k);
scanf("%s", s + 1);
// 初始化:空串是合法的全*串
for (int i = 1; i <= n; i++) {
dp[i][i - 1][0] = 1;
}
// 区间DP
for (int len = 1; len <= n; len++) {
for (int l = 1; l + len - 1 <= n; l++) {
int r = l + len - 1;
// 状态0: 全*串,长度≤k
if (len <= k) {
dp[l][r][0] = dp[l][r - 1][0] && (s[r] == '*' || s[r] == '?');
}
// 长度≥2才可能形成括号序列
if (len >= 2) {
// 状态1: (A)形式
if (check(s[l], s[r])) {
dp[l][r][1] = (dp[l + 1][r - 1][0] + dp[l + 1][r - 1][2] +
dp[l + 1][r - 1][3] + dp[l + 1][r - 1][4]) % mod;
}
// 枚举分割点
for (int i = l; i < r; i++) {
// 状态2: A*B形式,以*结尾
// A是状态3,B是状态0
dp[l][r][2] = (dp[l][r][2] + dp[l][i][3] * dp[i + 1][r][0]) % mod;
// 状态3: 完整合法序列
// (A是状态2或3) * B是状态1
dp[l][r][3] = (dp[l][r][3] + (dp[l][i][2] + dp[l][i][3]) * dp[i + 1][r][1]) % mod;
// 状态4: (A*B)形式,以)结尾
// A是状态4或5,B是状态1
dp[l][r][4] = (dp[l][r][4] + (dp[l][i][4] + dp[l][i][5]) * dp[i + 1][r][1]) % mod;
// 状态5: A*形式,以*结尾
// A是状态4,B是状态0
dp[l][r][5] = (dp[l][r][5] + dp[l][i][4] * dp[i + 1][r][0]) % mod;
}
}
// 状态5也可以直接是全*串
dp[l][r][5] = (dp[l][r][5] + dp[l][r][0]) % mod;
// 状态3也可以直接是状态1
dp[l][r][3] = (dp[l][r][3] + dp[l][r][1]) % mod;
}
}
// 答案:整个字符串是完整合法序列
printf("%lld\n", dp[1][n][3]);
return 0;
}
功能分析
1. 状态设计
- 状态0:处理全星号串,限制长度≤k
- 状态1:处理最基础的括号包裹形式
(A) - 状态2:处理
A*形式的拼接(右边是星号串) - 状态3:处理完整的合法序列(最终答案)
- 状态4:处理
(A*B)形式的中间状态 - 状态5:处理
A*形式的另一状态
2. 转移逻辑
- 避免重复计算:通过严格的状态划分,确保每种合法序列只被计算一次
- 处理拼接规则 :通过枚举分割点
i,处理各种拼接情况 - 处理括号嵌套:通过状态1处理括号嵌套的情况
3. 时间复杂度
- 三重循环:
O(n³),其中外层循环len,内层循环l和分割点i - 对于
n=500,大约需要500³ = 1.25亿次运算,可接受
4. 空间复杂度
dp[N][N][6]:500×500×6 = 150万个long long,约12MB内存
5. 正确性保证
- 完全按照题目给出的4条规则进行状态转移
- 状态3是最终答案,包含所有合法序列
- 通过取模运算防止溢出
专栏推荐:信奥赛C++提高组csp-s初赛&复赛真题题解(持续更新)
https://blog.csdn.net/weixin_66461496/category_13125089.html
各种学习资料,助力大家一站式学习和提升!!!
cpp
#include<bits/stdc++.h>
using namespace std;
int main(){
cout<<"########## 一站式掌握信奥赛知识! ##########";
cout<<"############# 冲刺信奥赛拿奖! #############";
cout<<"###### 课程购买后永久学习,不受限制! ######";
return 0;
}
1、csp信奥赛高频考点知识详解及案例实践:
CSP信奥赛C++动态规划:
https://blog.csdn.net/weixin_66461496/category_13096895.html点击跳转
CSP信奥赛C++标准模板库STL:
https://blog.csdn.net/weixin_66461496/category_13108077.html 点击跳转
信奥赛C++提高组csp-s知识详解及案例实践:
https://blog.csdn.net/weixin_66461496/category_13113932.html
2、csp信奥赛冲刺一等奖有效刷题题解:
CSP信奥赛C++初赛及复赛高频考点真题解析(持续更新):https://blog.csdn.net/weixin_66461496/category_12808781.html 点击跳转
CSP信奥赛C++一等奖通关刷题题单及题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12673810.html 点击跳转
信奥赛C++提高组csp-s初赛&复赛真题题解(持续更新)
https://blog.csdn.net/weixin_66461496/category_13125089.html
3、GESP C++考级真题题解:

GESP(C++ 一级+二级+三级)真题题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12858102.html 点击跳转

GESP(C++ 四级+五级+六级)真题题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12869848.html 点击跳转

GESP(C++ 七级+八级)真题题解(持续更新):
https://blog.csdn.net/weixin_66461496/category_13117178.html
4、CSP信奥赛C++竞赛拿奖视频课:
https://edu.csdn.net/course/detail/40437 点击跳转

· 文末祝福 ·
cpp
#include<bits/stdc++.h>
using namespace std;
int main(){
cout<<"跟着王老师一起学习信奥赛C++";
cout<<" 成就更好的自己! ";
cout<<" csp信奥赛一等奖属于你! ";
return 0;
}