CSP-S2021提高组第二轮T2:括号序列

2023-11-30 18:04

本文主要是介绍CSP-S2021提高组第二轮T2:括号序列,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

题目链接

[CSP-S 2021] 括号序列

题目描述

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

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

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

  1. ()(S) 均是符合规范的超级括号序列,其中 S 表示任意一个仅由不超过 k \bm{k} 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】

如下几种方案是符合规范的:

(**)*()
(**(*))
(*(**))
(*)**()
(*)(**)

【数据范围】

测试点编号 n ≤ n \le n特殊性质
1 ∼ 3 1 \sim 3 13 15 15 15
4 ∼ 8 4 \sim 8 48 40 40 40
9 ∼ 13 9 \sim 13 913 100 100 100
14 ∼ 15 14 \sim 15 1415 500 500 500 S S S 串中仅含有字符 ?
16 ∼ 20 16 \sim 20 1620 500 500 500

对于 100 % 100 \% 100% 的数据, 1 ≤ k ≤ n ≤ 500 1 \le k \le n \le 500 1kn500

算法思想

根据题目描述,符合规范的超级括号序列有下面几种情况:

  • ()
  • (S),将S加上一层括号是符合规范的。其中 S 表示任意一个仅由不超过 k \bm{k} k 字符 * 组成的非空字符串;
  • (A)表示对一个符合规范的序列再加一层括号也符合规范的
  • (SA)(AS)表示在一个符合规范的序列左边(或右边)连接一个S再加一层括号也是符合规范的
  • AB表示并排在一起的符合规范的序列也是合法的
  • ASB表示将S放在并排在一起的符合规范的序列之中也是合法的

k = 3 k = 3 k=3,则字符串 ((**()*(*))*)(***)是否合法可以这样分析:

  • 字符串的右边(***)是一个合法的序列(S),如果左边的子串((**()*(*))*)是一个合法序列,则并排在一起符合规范AB
  • ((**()*(*))*)如果其中的子串(**()*(*))是一个合法序列,则符合规范(AS)
  • (**()*(*))如果其中的子串()*(*)是一个合法序列,则符合规范(SA)
  • ()*(*)符合规范ASB,是一个合法序列

因此整个序列是符合规范的超级括号序列。

也就是说,对于字符串中任意一个区间 [ i , j ] [i,j] [i,j]判断是否符合规范,则需要判断其子区间是否符合规范,那么可以使用区间动态规划来处理。

状态表示

f [ i ] [ j ] f[i][j] f[i][j]表示将字符串中从 i i i j j j位置的字符确定后,得到的字符串是一个符合规范的超级括号序列的方案数。由于从 i i i j j j位置的字符可能是其中的一个子串,因此存在多种合法的状态:

  1. f [ i ] [ j ] [ 0 ] f[i][j][0] f[i][j][0]表示由单独的符合规范的超级括号序列组成的方案数,形式如()(S)(A)(SA)(AS)
  2. f [ i ] [ j ] [ 1 ] f[i][j][1] f[i][j][1]表示由并排的符合规范的超级括号序列组成的方案数,形式如ABASB
  3. f [ i ] [ j ] [ 2 ] f[i][j][2] f[i][j][2]表示仅由不超过 k \bm{k} k 字符 * 组成的方案数,形式如***…。需要注意的是只有 * 组成的子串无法单独构成符合规范的超级括号序列。
  4. f [ i ] [ j ] [ 3 ] f[i][j][3] f[i][j][3]表示由符合规范的超级括号序列+不超过 k \bm{k} k * 组成的方案数,形式如AS。该方案也无法单独构成符合规范的超级括号序列。
  5. f [ i ] [ j ] [ 4 ] f[i][j][4] f[i][j][4]表示由不超过 k \bm{k} k * + 符合规范的超级括号序列组成的方案数,形式如SA。该方案也无法单独构成符合规范的超级括号序列。

最终的方案总数为 f [ 1 ] [ n ] [ 0 ] + f [ 1 ] [ n ] [ 1 ] f[1][n][0]+f[1][n][1] f[1][n][0]+f[1][n][1], 其中 n n n表示字符串 S S S的长度。

状态计算

  • f [ i ] [ j ] [ 0 ] f[i][j][0] f[i][j][0]表示由单独的符合规范的超级括号序列组成的方案数,那么去掉 i i i j j j位置的括号后,子串的形式可以是空串AABSSAAS的任意一种情况,因此, f [ i ] [ j ] [ 0 ] = f [ i + 1 ] [ j − 1 ] [ 0 ] + f [ i + 1 ] [ j − 1 ] [ 1 ] + f [ i + 1 ] [ j − 1 ] [ 2 ] + f [ i + 1 ] [ j − 1 ] [ 3 ] + f [ i + 1 ] [ j − 1 ] [ 4 ] f[i][j][0]=f[i+1][j-1][0]+f[i+1][j-1][1]+f[i+1][j-1][2]+f[i+1][j-1][3]+f[i+1][j-1][4] f[i][j][0]=f[i+1][j1][0]+f[i+1][j1][1]+f[i+1][j1][2]+f[i+1][j1][3]+f[i+1][j1][4]

    注意:当长度为 2 2 2时,也就是只有括号的情况下,子串为空, f [ i ] [ j ] [ 0 ] = 1 f[i][j][0] = 1 f[i][j][0]=1

  • f [ i ] [ j ] [ 1 ] f[i][j][1] f[i][j][1]表示由并排的符合规范的超级括号序列组成的方案数,如果以一个符合规范的超级括号序列B进行分类,那么子串的形式可以是AAS。因此可以枚举最后一个B和前面的分界点 k k k f [ i ] [ j ] [ 1 ] = ∑ k = i j − 1 ( f [ i ] [ k ] [ 0 ] + f [ i ] [ k ] [ 1 ] + f [ i ] [ k ] [ 3 ] ) × f [ k + 1 ] [ j ] [ 0 ] f[i][j][1]=\sum_{k=i}^{j-1}(f[i][k][0]+f[i][k][1]+f[i][k][3])\times f[k+1][j][0] f[i][j][1]=k=ij1(f[i][k][0]+f[i][k][1]+f[i][k][3])×f[k+1][j][0]

  • f [ i ] [ j ] [ 2 ] f[i][j][2] f[i][j][2]表示仅由 * 组成的方案数,只能从状态 [ 2 ] [2] [2]转移过来,所以 f [ i ] [ j ] [ 2 ] = f [ i + 1 ] [ j ] [ 2 ] f[i][j][2]=f[i+1][j][2] f[i][j][2]=f[i+1][j][2]

    注意:当长度为 1 1 1时,且只有*的情况下, f [ i ] [ j ] [ 2 ] = 1 f[i][j][2] = 1 f[i][j][2]=1

  • f [ i ] [ j ] [ 3 ] f[i][j][3] f[i][j][3]表示由符合规范的超级括号序列+* 组成的方案数,以后面的S进行分类,前面的子串形式可以是AAB,因此可以枚举S和前面的分界点 k k k f [ i ] [ j ] [ 3 ] = ∑ k = i j − 1 ( f [ i ] [ k ] [ 0 ] + f [ i ] [ k ] [ 1 ] ) × f [ k + 1 ] [ j ] [ 2 ] f[i][j][3]=\sum_{k=i}^{j-1}(f[i][k][0]+f[i][k][1]) \times f[k+1][j][2] f[i][j][3]=k=ij1(f[i][k][0]+f[i][k][1])×f[k+1][j][2]

  • f [ i ] [ j ] [ 4 ] f[i][j][4] f[i][j][4]表示由* + 符合规范的超级括号序列组成的方案数,以前面的S进行分类,前面的子串形式可以是AAB,因此可以枚举S和后面的分界点 k k k f [ i ] [ j ] [ 4 ] = ∑ k = i j − 1 f [ i ] [ k ] [ 2 ] × ( f [ k + 1 ] [ j ] [ 0 ] + f [ k + 1 ] [ j ] [ 1 ] ) f[i][j][4]=\sum_{k=i}^{j-1} f[i][k][2] \times (f[k+1][j][0]+f[k+1][j][1]) f[i][j][4]=k=ij1f[i][k][2]×(f[k+1][j][0]+f[k+1][j][1])

时间复杂度

  • 状态数为 n × n × 5 n\times n\times5 n×n×5
  • 状态计算需要枚举分界位置,时间复杂度最大为 O ( n ) O(n) O(n)

总的时间复杂度为 O ( 5 × n 3 ) = 625 , 000 , 000 O(5\times n^3)=625,000,000 O(5×n3)=625,000,000,由于枚举区间起点和分界位置的时间复杂度要低于 O ( n ) O(n) O(n),刚好可以AC。

代码实现

#include <iostream>
using namespace std;
typedef long long LL;
const int N = 510, MOD = 1e9 + 7;
char s[N];
/*
f[i][j][0]表示由单独的符合规范的序列组成的方案数
f[i][j][1]表示由并排的符合规范的序列组成的方案数
f[i][j][2]表示仅由*组成的方案数
f[i][j][3]表示由符合规范的序列 + *组成的方案数
f[i][j][4]表示由* + 符合规范的序列组成的方案数
*/
int f[N][N][5];
//判断a和b是否匹配
bool is_match(char a, char b)
{return a == b || a == '?';
}
int main()
{int n, m;cin >> n >> m;cin >> s + 1; //字符串下标从1开始//枚举区间长度for(int len = 1; len <= n; len ++){//枚举区间开始位置for(int i = 1; i + len - 1 <= n; i ++){int j = i + len - 1; //结束位置if(len == 1) //区间长度为1{//只有1个星号if(is_match(s[i], '*')) f[i][j][2] = 1;}else{//计算状态[0],由单独的符合规范的序列组成的方案数if(is_match(s[i], '(') && is_match(s[j], ')')){//当长度为2时,也就是只有括号的情况下,子串为空if(len == 2) f[i][j][0] = 1;//[0] = [0] + [1] + [2] + [3] + [4]for(int k = 0; k < 5; k ++)f[i][j][0] = (f[i][j][0] + f[i + 1][j - 1][k]) % MOD;}//计算状态[2],仅由*组成的方案数if(len <= m && is_match(s[i], '*'))f[i][j][2] = f[i + 1][j][2];//枚举分界位置,计算其它状态[1]、[3]、[4]for(int k = i; k < j; k ++){f[i][j][1] = (f[i][j][1] + (f[i][k][0] + (LL)f[i][k][1] + f[i][k][3]) * f[k + 1][j][0]) % MOD;f[i][j][3] = (f[i][j][3] + (f[i][k][0] + (LL)f[i][k][1]) * f[k + 1][j][2]) % MOD;f[i][j][4] = (f[i][j][4] + f[i][k][2] * ((LL)f[k + 1][j][0] + f[k + 1][j][1])) % MOD;}}}}cout << (f[1][n][0] + f[1][n][1]) % MOD;return 0;
}

这篇关于CSP-S2021提高组第二轮T2:括号序列的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/438006

相关文章

C++使用栈实现括号匹配的代码详解

《C++使用栈实现括号匹配的代码详解》在编程中,括号匹配是一个常见问题,尤其是在处理数学表达式、编译器解析等任务时,栈是一种非常适合处理此类问题的数据结构,能够精确地管理括号的匹配问题,本文将通过C+... 目录引言问题描述代码讲解代码解析栈的状态表示测试总结引言在编程中,括号匹配是一个常见问题,尤其是在

最长公共子序列问题的深度分析与Java实现方式

《最长公共子序列问题的深度分析与Java实现方式》本文详细介绍了最长公共子序列(LCS)问题,包括其概念、暴力解法、动态规划解法,并提供了Java代码实现,暴力解法虽然简单,但在大数据处理中效率较低,... 目录最长公共子序列问题概述问题理解与示例分析暴力解法思路与示例代码动态规划解法DP 表的构建与意义动

关于最长递增子序列问题概述

《关于最长递增子序列问题概述》本文详细介绍了最长递增子序列问题的定义及两种优化解法:贪心+二分查找和动态规划+状态压缩,贪心+二分查找时间复杂度为O(nlogn),通过维护一个有序的“尾巴”数组来高效... 一、最长递增子序列问题概述1. 问题定义给定一个整数序列,例如 nums = [10, 9, 2

Golang的CSP模型简介(最新推荐)

《Golang的CSP模型简介(最新推荐)》Golang采用了CSP(CommunicatingSequentialProcesses,通信顺序进程)并发模型,通过goroutine和channe... 目录前言一、介绍1. 什么是 CSP 模型2. Goroutine3. Channel4. Channe

如何提高Redis服务器的最大打开文件数限制

《如何提高Redis服务器的最大打开文件数限制》文章讨论了如何提高Redis服务器的最大打开文件数限制,以支持高并发服务,本文给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧... 目录如何提高Redis服务器的最大打开文件数限制问题诊断解决步骤1. 修改系统级别的限制2. 为Redis进程特别设置限制

uva 10131 最长子序列

题意: 给大象的体重和智商,求体重按从大到小,智商从高到低的最长子序列,并输出路径。 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstring>#include <cmath>#include <stack>#include <vect

键盘快捷键:提高工作效率与电脑操作的利器

键盘快捷键:提高工作效率与电脑操作的利器 在数字化时代,键盘快捷键成为了提高工作效率和优化电脑操作的重要工具。无论是日常办公、图像编辑、编程开发,还是游戏娱乐,掌握键盘快捷键都能带来极大的便利。本文将详细介绍键盘快捷键的概念、重要性、以及在不同应用场景中的具体应用。 什么是键盘快捷键? 键盘快捷键,也称为热键或快捷键,是指通过按下键盘上的一组键来完成特定命令或操作的方式。这些快捷键通常涉及同

POJ1631最长单调递增子序列

最长单调递增子序列 import java.io.BufferedReader;import java.io.InputStream;import java.io.InputStreamReader;import java.io.PrintWriter;import java.math.BigInteger;import java.util.StringTokenizer;publ

CSP 2023 提高级第一轮 CSP-S 2023初试题 完善程序第二题解析 未完

一、题目阅读 (最大值之和)给定整数序列 a0,⋯,an−1,求该序列所有非空连续子序列的最大值之和。上述参数满足 1≤n≤105 和 1≤ai≤108。 一个序列的非空连续子序列可以用两个下标 ll 和 rr(其中0≤l≤r<n0≤l≤r<n)表示,对应的序列为 al,al+1,⋯,ar​。两个非空连续子序列不同,当且仅当下标不同。 例如,当原序列为 [1,2,1,2] 时,要计算子序列 [

leetcode105 从前序与中序遍历序列构造二叉树

根据一棵树的前序遍历与中序遍历构造二叉树。 注意: 你可以假设树中没有重复的元素。 例如,给出 前序遍历 preorder = [3,9,20,15,7]中序遍历 inorder = [9,3,15,20,7] 返回如下的二叉树: 3/ \9 20/ \15 7   class Solution {public TreeNode buildTree(int[] pr