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

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

题目描述

小 w 在赛场上遇到了这样一个题:一个长度为 n n n 且符合规范的括号序列,其有些位置已经确定了,有些位置尚未确定,求这样的括号序列一共有多少个。

身经百战的小 w 当然一眼就秒了这题,不仅如此,他还觉得一场正式比赛出这么简单的模板题也太小儿科了,于是他把这题进行了加强之后顺手扔给了小 c。

具体而言,小 w 定义"超级括号序列"是由字符 ()* 组成的字符串,并且对于某个给定的常数 k k k,给出了"符合规范的超级括号序列"的定义如下:

  1. ()(S) 均是符合规范的超级括号序列,其中 S 表示任意一个仅由不超过 k个字符 * 组成的非空字符串(以下两条规则中的 S 均为此含义);
  2. 如果字符串 AB 均为符合规范的超级括号序列,那么字符串 ABASB 均为符合规范的超级括号序列,其中 AB 表示把字符串 A 和字符串 B 拼接在一起形成的字符串;
  3. 如果字符串 A 为符合规范的超级括号序列,那么字符串 (A)(SA)(AS) 均为符合规范的超级括号序列。
  4. 所有符合规范的超级括号序列均可通过上述 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] 全是 * 且长度 ≤ k
  • dp[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* 形式,且以 * 结尾
转移方程
  1. 状态0 :全是 * 且长度 ≤ k

    • dp[l][r][0] = dp[l][r-1][0] && (s[r]=='*'||s[r]=='?')
  2. 状态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]
  3. 状态2A*B 形式,以 * 结尾

    • 枚举分割点 i,要求 [i+1, r] 是状态0
    • dp[l][r][2] += dp[l][i][3] * dp[i+1][r][0]
  4. 状态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]
  5. 状态4(A*B) 形式,以 ) 结尾

    • dp[l][r][4] += (dp[l][i][4] + dp[l][i][5]) * dp[i+1][r][1]
  6. 状态5A* 形式,以 * 结尾

    • 从状态0直接转移:dp[l][r][5] += dp[l][r][0]
    • 从拼接转移:dp[l][r][5] += dp[l][i][4] * dp[i+1][r][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;
}
相关推荐
王老师青少年编程1 小时前
2021信奥赛C++提高组csp-s复赛真题及题解:回文
c++·真题·回文·信奥赛·csp-s·提高组·复赛
0 0 02 小时前
【C++】矩阵翻转/n*n的矩阵旋转
c++·线性代数·算法·矩阵
sycmancia2 小时前
C++——类的真正形态、构造函数的调用
开发语言·c++
CHANG_THE_WORLD2 小时前
C/C++字符串定义的五种写法 和 C/C++字符串隐藏技术深度剖析
c++
sycmancia2 小时前
C++——初始化列表的使用
开发语言·c++
白太岁2 小时前
Redis:(3) Lua 与 Redis、基于连接池的 Facade 模式封装
数据库·c++·redis·lua·外观模式
『往事』&白驹过隙;2 小时前
系统编程的内存零拷贝(Zero-Copy)技术
linux·c语言·网络·c++·物联网·iot
量子炒饭大师3 小时前
【C++入门】Cyber高维的蜂巢意识 —— 【类与对象】static 成员
开发语言·c++·静态成员变量·static成员
ShineWinsu3 小时前
对于stack和queue经典算法题目:155. 最小栈、JZ31 栈的压入、弹出序列和102. 二叉树的层序遍历的解析
数据结构·c++·算法·面试·力扣·笔试·牛客网