草庐IT

12届蓝桥杯省赛c++b组 J题 括号序列

thejohn2020 2023-10-22 原文

这次要讲的前几个星期刚比完的蓝桥杯c++b组J题:括号序列。本次比赛我也参加了,但是这道题我是dfs求解的,所以都只是拿了少部分的分,我比赛时的代码就不展示了,因为时间复杂度很高,所以我就直接讲解正解应该怎么写了。

先上题目:

给定一个括号序列,要求尽可能少地添加若干括号使得括号序列变得合法,当添加完成后,会产生不同的添加结果,请问有多少种本质不同的添加结果。

两个结果是本质不同的是指存在某个位置一个结果是左括号,而另一个是右括号。

例如,对于括号序列 (((),只需要添加两个括号就能让其合法,有以下几种不同的添加结果:()()()、()(())、(())()、(()()) 和 ((()))。

输入格式
输入一行包含一个字符串 s,表示给定的括号序列,序列中只有左括号和右括号。

输出格式
输出一个整数表示答案,答案可能很大,请输出答案除以 1000000007 (即 10^9+7) 的余数。

数据范围
对于 40% 的评测用例,|s|≤200。 对于所有评测用例,1≤|s|≤5000。

Sample Input:

((()

Sample output

5

这道题呢就是一个括号匹配的问题,其实这种题目也是非常经典的一类题目。因为我们刚开始认识栈的时候,老师都会举括号匹配的例子来说明栈的应用。这种括号的题目是必须遵守一个规则的:在任何时候,一个合法的括号序列都满足左括号的数量大于等于右括号的数量!

举一个例子:()))这个括号序列在 i=2,3的时候都是不合法的(下标从0开始计数),而((()这个括号序列在当前都是合法的,这是因为左括号匹配右括号,如果只有右括号没有与之对应的左括号的话就必须得在这个右括号之前加上一个左括号。而如果左括号比右括号多的话,在这个位置之后可能还会有右括号和前面的左括号对应,所以我们不一定非得在这个时候加上右括号。

那么解决这个题目的算法是什么呢?这道题存在大量的重叠子问题,如果dfs一个个求的话,我们的时间复杂度会很高,再来看看我们的数据范围1≤|s|≤5000,也就是我们必须得设计一个O(n^2)以内的算法才能通过所有的测试用例,所以这道题可以使用动态规划来解决。

我们该如何定义状态?其实我们可以先看左括号,用f[i][j]来表示我们遍历到第 i 个括号,此时左括号比右括号多 j 个的时候的添加括号方案数。先算出添加左括号的方案数,再从右到左算出添加右括号的方案数,最后两个相乘就是我们的结果。

其实对于这道题来说,我们不需要写两个函数分别求左括号和右括号方案数,我们可以先算出左括号方案数,然后把这个字符串进行翻转,然后将左括号换成右括号,右括号换成左括号再求一次这个函数就是右括号的方案数。这是为什么呢?我们可以倒着看一下,上面的定理对于倒着依旧是相对成立的,倒着看的时候,一个合法的括号序列都满足右括号的数量大于等于左括号的数量。具体可以把上面的论点逆着推一下。

可能会有人有疑问,为什么两个方案数是相乘的关系,对于两个括号中间加入左右括号,难道里面的括号重新排列不是另一种方案吗?其实他们之间是相对独立的,我们可以这样想,…… |…… 如果我们在 | 的地方加入一个左括号,一个右括号的话,只能形成 ) ( 这样的序列,不能形成这样(),这是因为如果是后者的话,这算一对括号,可以抵消掉,还多了一对,题目的要求是尽可能少地添加若干括号,所以无论怎样,都只会有一种排列方式。

既然这样的话,我们怎么保证添加的时候不重复呢?也就是(((我们算成了好几种,其实只有一种。我们可以以右括号为界限,如果以右括号为界限,比如这样……)……)……,其实……都是左括号,他们无论怎样排列都是一样的。

至此我们的状态转移方程就好写了,我们只考虑在右括号的前面添加n个左括号,因为其他时候添加左括号其实都一样(里面全是左括号的话怎么排列都是一样的)。当我们遇到左括号的时候非常简单,直接把这个左括号加进来,就是比前一个状态多了1个左括号,f[i][j]=f[i-1][j-1]。比如我们前面左括号比右括号多5个,那么现在我们遇到的是左括号,我们的左括号就比右括号多了6个。

当我们遇到的是右括号的时候,我们考虑在右括号前面添加n个左括号,从0开始枚举,我们前面加0个左括号就是f[i][j]=f[i-1][j+1],因为前面左括号比右括号多j+1个,现在遇到右括号了,我们不加左括号的情况下就多了j个。如果加1个左括号的话f[i][j]=f[i-1][j],因为前面左括号比右括号多j个,现在遇到右括号了,我们加1个左括号的情况下就多了j个,以此类推。

所以f[i][j]就是前面添加左括号的所有情况相加,即f[i][j]=f[i-1][j+1]+f[i-1][j]+f[i-1][j-1]+……+f[i-1][0]。我们会发现,如果这样子加的话我们时间复杂度就是O(n),会使得整体时间复杂度变成O(n^3)会超时。

不过我们可以发现f[i][j-1]=f[i-1][j]+f[i-1][j]+f[i-1][j-1]+……+f[i-1][0],这样的话f[i][j]=f[i-1][j+1]+f[i][j-1]就会是O(1)的时间复杂度了,思路就到这里了,我附上代码:

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
using LL=long long;
const int N = 5005;
int f[N][N];
int mod=1e9+7;
string s;
int n;
LL get(){
    memset(f,0,sizeof f);
    f[0][0]=1;
    for(int i=1;i<=n;i++){
        if(s[i-1]=='('){
            for(int j=1;j<=n;j++)
                f[i][j]=f[i-1][j-1];
        }
        else{
            f[i][0]=(f[i-1][1]+f[i-1][0])%mod;
            for(int j=1;j<=n;j++)
                f[i][j]=(f[i-1][j+1]+f[i][j-1])%mod;
        }
    }
    for(int i=0;i<=n;i++)
        if(f[n][i])
            return f[n][i];
    return -1;
}
int main(){
    cin>>s;
    n=s.size();
    LL x=get();
    reverse(s.begin(),s.end());
    for(int i=0;i<n;i++){
        if(s[i]==')')
            s[i]='(';
        else
            s[i]=')';
    }
    LL y=get();
    cout<<(x*y)%mod;
}

base case就是f[0][0]=1,即我们看到前0个括号,左括号比右括号多0个的方案数为1,也是符合实际的。注意在遇到右括号的时候我们要特别处理一下f[i][0],这是因为根据上面的状态转移方程,当j=0时f[i][j-1]会越界,我们就用上面O(n)的方法对f[i][0]进行特殊处理,因为只有两项,所以也是O(1)的时间复杂度。

值得一提的是,最后求答案的时候我们得找出f[n][i]中不为0的作为答案。有些同学就有疑问了,为什么不取f[n][0]?
我举一个例子:((((
i=1时,f[1][1]=1,其余f[1][x]都为0;
i=2时,f[2][2]=1,其余f[2][x]都为0;
i=3时,f[3][3]=1,其余f[3][x]都为0;
i=4时,f[4][4]=1,其余f[4][x]都为0;
所以如果我们求f[n][0]的时候方案数就是0,这是不对的。根本不存在左括号比右括号多0个情况,这是因为我们只添加左括号,不添加右括号

不过不要忘记对结果取模哦。

补:

应粉丝要求,我把比赛时写的dfs代码附上,不过会TLE。各位参考一下就好

#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_set>
using namespace std;

int n;
string s;
unordered_set<string> ret;

void dfs(int l,int r,int al,int ar,int idx,string cur){
    if(idx==n){
        if(!al&&!ar)
            ret.insert(cur);
        return;
    }
    if(al)
        dfs(l+1,r,al-1,ar,idx,cur+'(');
    if(ar&&l>r)
        dfs(l,r+1,al,ar-1,idx,cur+')');
    if(s[idx]=='(')
        dfs(l+1,r,al,ar,idx+1,cur+'(');
    else if(l>r)
        dfs(l,r+1,al,ar,idx+1,cur+')');
}

int main(){
    cin>>s;
    n=s.size();
    int al=0,ar=0,l=0;
    for(char c:s){
        if(c=='('){
            l++;
        }
        else if(c==')'){
            if(l==0)
                al++;
            else{
                l--;
            }
        }
    }
    ar=l;
    dfs(0,0,al,ar,0,"");
    cout<<ret.size();
}

这道题就讲解到这里啦,继续加油:)

有关12届蓝桥杯省赛c++b组 J题 括号序列的更多相关文章

  1. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

  2. ruby - 带括号和 splat 运算符的并行赋值 - 2

    我明白了:x,(y,z)=1,*[2,3]x#=>1y#=>2z#=>nil我想知道为什么z的值为nil。 最佳答案 x,(y,z)=1,*[2,3]右侧的splat*是内联扩展的,所以它等同于:x,(y,z)=1,2,3左边带括号的列表被视为嵌套赋值,所以它等价于:x=1y,z=23被丢弃,而z被分配给nil。 关于ruby-带括号和splat运算符的并行赋值,我们在StackOverflow上找到一个类似的问题: https://stackoverflow

  3. ruby - 在 Ruby 中比较序列 - 2

    假设我必须(小型到中型)阵列:tokens=["aaa","ccc","xxx","bbb","ccc","yyy","zzz"]template=["aaa","bbb","ccc"]如何确定tokens是否以相同的顺序包含template的所有条目?(请注意,在上面的示例中,应忽略第一个“ccc”,从而由于最后一个“ccc”而导致匹配。) 最佳答案 这适用于您的示例数据。tokens=["aaa","ccc","xxx","bbb","ccc","yyy","zzz"]template=["aaa","bbb","ccc"]po

  4. Ruby 正则表达式匹配逗号,但忽略括号中的逗号 - 2

    我正在尝试通过正则表达式拆分参数列表。这是一个带有我的参数列表的字符串:"a=b,c=3,d=[1,3,5,7],e,f=g"我想要的是:["a=b","c=3","d=[1,3,5,7]","e","f=g"]我试过先行,但Ruby不允许使用动态范围后行,所以这行不通:/(?如何让正则表达式忽略方括号中的所有内容? 最佳答案 也许这样的东西对你有用:str.scan(/(?:\[.*?\]|[^,])+/)编辑再三考虑。简单的非贪婪匹配器在某些嵌套括号的情况下会失败。 关于Ruby正则

  5. ruby - 如何在ruby中提取方括号内的内容 - 2

    我正在尝试提取方括号内的内容。到目前为止,我一直在使用它,它有效,但我想知道我是否可以直接在正则表达式中使用某些东西,而不是使用这个删除功能。a="Thisissuchagreatday[coolawesome]"a[/\[.*?\]/].delete('[]')#=>"coolawesome" 最佳答案 差不多。a="Thisissuchagreatday[coolawesome]"a[/\[(.*?)\]/,1]#=>"coolawesome"a[/(?"coolawesome"第一个依赖于提取组而不是完全匹配;第二个利用前瞻和

  6. ruby-on-rails - carrierwave:在序列化动态属性上安装 uploader - 2

    首先,我使用的是rails3.1.3和来自master的carrierwavegithub仓库的分支。我使用after_init钩子(Hook)来确定基于属性的字段页面模型实例并为这些字段定义属性访问器将值存储在序列化哈希中(希望它清楚我是什么谈论)。这是我正在做的事情的精简版:classPage省略mount_uploader命令让我可以访问我想要的属性。但是当我安装uploader时出现错误消息说“nil类的未定义新方法”我在源代码中读到有方法read_uploader和扩展模块中的write_uploader。我如何必须覆盖这些来制作mount_uploader命令使用我的“虚拟

  7. 深度学习12. CNN经典网络 VGG16 - 2

    深度学习12.CNN经典网络VGG16一、简介1.VGG来源2.VGG分类3.不同模型的参数数量4.3x3卷积核的好处5.关于学习率调度6.批归一化二、VGG16层分析1.层划分2.参数展开过程图解3.参数传递示例4.VGG16各层参数数量三、代码分析1.VGG16模型定义2.训练3.测试一、简介1.VGG来源VGG(VisualGeometryGroup)是一个视觉几何组在2014年提出的深度卷积神经网络架构。VGG在2014年ImageNet图像分类竞赛亚军,定位竞赛冠军;VGG网络采用连续的小卷积核(3x3)和池化层构建深度神经网络,网络深度可以达到16层或19层,其中VGG16和VGG

  8. 机器学习——时间序列ARIMA模型(四):自相关函数ACF和偏自相关函数PACF用于判断ARIMA模型中p、q参数取值 - 2

    文章目录1、自相关函数ACF2、偏自相关函数PACF3、ARIMA(p,d,q)的阶数判断4、代码实现1、引入所需依赖2、数据读取与处理3、一阶差分与绘图4、ACF5、PACF1、自相关函数ACF自相关函数反映了同一序列在不同时序的取值之间的相关性。公式:ACF(k)=ρk=Cov(yt,yt−k)Var(yt)ACF(k)=\rho_{k}=\frac{Cov(y_{t},y_{t-k})}{Var(y_{t})}ACF(k)=ρk​=Var(yt​)Cov(yt​,yt−k​)​其中分子用于求协方差矩阵,分母用于计算样本方差。求出的ACF值为[-1,1]。但对于一个平稳的AR模型,求出其滞

  9. 蓝桥杯备赛(二) - 2

    目录前言: 一、ASC分析代码实现二、 卡片分析代码实现三、 直线分析代码实现四、货物摆放分析代码实现小结:前言:  在刷题的过程中,发现蓝桥杯的题目和力扣的差别很大。让人有一种不一样的感觉,蓝桥杯题目偏向对于实际问题用编程去的解决,而力扣给人感觉很锻炼自己的编程思维,逻辑能力。两者结合去刷,相信会有不一样的收获。 一、ASC  已知大写字母A的ASCII码为65,请问大写字母L的ASCII码是多少?分析  这道题目看上去很简单,我们需确定自己计算的准确,所以我建议用编程去解决。代码实现publicclassTest8{publicstaticvoidmain(String[]args){Sy

  10. ruby-on-rails - 无法构建 gem native 扩展 (mkmf (LoadError)) - Ubuntu 12.04 - 2

    这个问题在这里已经有了答案:Unabletoinstallgem-Failedtobuildgemnativeextension-cannotloadsuchfile--mkmf(LoadError)(17个答案)关闭9年前。嘿,我正在尝试在一台新的ubuntu机器上安装rails。我安装了ruby​​和rvm,但出现“无法构建gemnative扩展”错误。这是什么意思?$sudogeminstallrails-v3.2.9(没有sudo表示我没有权限)然后它会输出很多“获取”命令,最终会出现这个错误:Buildingnativeextensions.Thiscouldtakeawhi

随机推荐