2026年9月GESP真题及题解(C++七级):括号序列

题目描述
对于字符串 S S S 与 T T T,如果从 S S S 中删除任意多个字符可以得到 T T T,那么 T T T 是 S S S 的子序列。换言之, T T T 是选取 S S S 中的若干字符按下标顺序连接而成的。两个子序列不同当且仅当所选取的下标不同。
例如 sun 是 sequence 的子序列,因为从 sequence 中删除 eq、e 和 ce 可以得到 sun;sequence 有 2 8 2^8 28 个不同的子序列,其中有空字符串,也有三个不同的子序列 e,因为 sequence 的第 2 , 5 , 8 2,5,8 2,5,8 个字符都为 e,分别保留这三个字符得到的子序列是不同的。
对于字符串 S S S,如果 S S S 满足以下条件那么 S S S 是合法括号序列:
- S S S 是空字符串,或者
- S S S 可由
(、合法括号序列、)三者连接得到,或者 - S S S 可由两个合法括号序列连接得到。
例如 ()、()()、(()) 和 (()()) 都是合法括号序列。但是 (()、)( 不是合法括号序列。
给定一个长度为 n n n 的仅包含 ( 与 ) 的字符串 S S S。请你求出 S S S 所有 2 n 2^n 2n 个子序列中有多少个合法括号序列。由于答案可能很大,请你输出答案对 10 9 10^9 109 取模的结果。
例如, S S S 为 ))(()( 时共有 3 3 3 个子序列是合法括号序列,分别为空字符串与两个不同的子序列 ()。
输入格式
第一行,一个正整数 n n n,表示字符串 S S S 的长度。
第二行,长度为 n n n 的仅包含 ( 与 ) 的字符串 S S S。
输出格式
输出一行,一个整数,表示 S S S 的合法括号子序列的数量对 10 9 10^9 109 取模的结果。
输入输出样例 1
输入 1
6
))(()(
输出 1
3
输入输出样例 2
输入 2
34
((((((((((((((((()))))))))))))))))
输出 2
333606220
说明/提示
对于 40 % 40\% 40% 的测试点,保证 1 ≤ n ≤ 400 1\le n\le 400 1≤n≤400。
对于所有测试点,保证 1 ≤ n ≤ 2000 1\le n\le 2000 1≤n≤2000。
思路分析
合法括号序列等价于:
- 任意前缀中,左括号数量不少于右括号数量;
- 最终左右括号数量相等。
把左右括号数量差记为"平衡值":
- 遇到
(,平衡值加一; - 遇到
),平衡值减一; - 合法括号序列要求任意时刻平衡值不小于 0,最终平衡值为 0。
由于题目统计的是"按下标选取不同"的子序列,因此对 S 中每个字符只需要考虑"选"或"不选"两种决策。每个选法唯一对应一个子序列,即使字符相同,只要下标不同就算不同子序列。
设 f[j] 表示当前处理到某个位置时,已经选出的子序列平衡值为 j,且过程中没有出现平衡值小于 0 的方案数。初始时选择空子序列,所以 f[0] = 1。
处理当前字符 c 时,用新数组 g 表示加入这个字符后的状态:
- 不选
c:g[j] += f[j]; - 若
c == '(',选它:g[j+1] += f[j]; - 若
c == ')',选它且j > 0:g[j-1] += f[j]。
处理完所有字符后,f[0] 就是所有合法括号子序列数量,其中包含空字符串。
时间复杂度为 O(n^2),空间复杂度为 O(n),可以轻松通过 n <= 2000。
代码实现
cpp
#include<bits/stdc++.h>
using namespace std;
const int M=1000000000;//模数
int n;
string s;
int main(){
cin>>n;
cin>>s;
vector<int> f(n+1,0),g(n+1,0);//旧状态和新状态
f[0]=1;//空串
for(int i=0;i<n;i++){//处理第i个字符
fill(g.begin(),g.end(),0);//清空新状态
for(int j=0;j<=i;j++){//当前平衡值
if(f[j]==0)continue;//无方案
g[j]=(g[j]+f[j])%M;//不选
if(s[i]=='(')g[j+1]=(g[j+1]+f[j])%M;//选左括号
else if(j>0)g[j-1]=(g[j-1]+f[j])%M;//选右括号
}
f.swap(g);//滚动数组
}
cout<<f[0]<<'\n';//答案
return 0;
}
功能分析
该程序使用动态规划统计字符串 S 所有子序列中合法括号序列的数量。
f[j] 表示当前平衡值为 j 且任意前缀平衡值不小于 0 的子序列方案数。每读入一个字符,分别考虑不选、选左括号、选右括号三种转移。最终 f[0] 即为所有合法括号子序列数量,包括空字符串。
算法时间复杂度为 O ( n 2 ) O(n^2) O(n2),空间复杂度为 O(n)。
各种学习资料,助力大家一站式学习和提升!!!
cpp
#include<bits/stdc++.h>
using namespace std;
int main(){
cout<<"########## 一站式掌握信奥赛知识! ##########";
cout<<"############# 冲刺信奥赛拿奖! #############";
cout<<"###### 课程购买后永久学习,不受限制! ######";
return 0;
}
【秘籍汇总】(完整csp信奥赛C++学习资料):
1、csp/信奥赛C++,完整信奥赛系列课程(永久学习):
https://edu.csdn.net/lecturer/7901 点击跳转

2、CSP信奥赛C++竞赛拿奖视频课:
https://edu.csdn.net/course/detail/40437 点击跳转

https://edu.csdn.net/course/detail/41081 点击跳转

3、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 点击跳转
4、csp信奥赛冲刺一等奖有效刷题题解:
信奥赛C++普及组CSP-J一等奖通关刷题题单及题解:
https://blog.csdn.net/weixin_66461496/category_12673810.html 点击跳转
信奥赛C++普及组csp-j初赛&复赛真题题解(持续更新): https://blog.csdn.net/weixin_66461496/category_12808781.html 点击跳转
信奥赛C++提高组csp-s初赛&复赛真题题解(持续更新):
https://blog.csdn.net/weixin_66461496/category_13125089.html 点击跳转
5、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 点击跳转
· 文末祝福 ·
cpp
#include<bits/stdc++.h>
using namespace std;
int main(){
cout<<"跟着王老师一起学习信奥赛C++";
cout<<" 成就更好的自己! ";
cout<<" csp信奥赛一等奖属于你! ";
return 0;
}
