第十二届蓝桥杯省赛c/c++B组 括号序列

题意:

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

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

例如,对于括号序列 (((),只需要添加两个括号就能让其合法,有以下几种不同的添加结果:()()()、()(())、(())()、(()()) 和 ((()))。
输入格式
输入一行包含一个字符串 s,表示给定的括号序列,序列中只有左括号和右括号。

输出格式
输出一个整数表示答案,答案可能很大,请输出答案除以 1000000007 的余数。

思路:

合法括号序列的定义: 对于任意前缀,左括号的数量 ≥ \geq 右括号的数量。
首先 n ≤ 5000 n\leq5000 n5000,那么我们要控制在 n 2 n^2 n2的复杂度内。
思考第一个问题 :添加左括号和右括号的方案数是否独立?
因为题目要求得是最小的添加数量,假设我们添加一对括号,肯定是无用的。我们先添加完左括号,再添加右括号,那么可以肯定的是只有 ) ) ( ( ) ) ( ( ))((这一种排列方式,否则的话会相互抵消为一对,那么我们的添加将毫无意义。所以得出结论 :二者相互独立,答案即为 a n s = 添加左括号的数量 × 添加右括号的数量 ans = 添加左括号的数量\times添加右括号的数量 ans=添加左括号的数量×添加右括号的数量, 分开计算即可。

接下来解决如何计算添加最少左括号的数量

根据合法括号序列的性质,我们只需在每个右括号之前添加左括号使其满足前缀性质。那么显然该序列被右括号分成了 c n t 右括号 + 1 cnt_{右括号} + 1 cnt右括号+1段,我们只需在每一段添加左括号使其合法。
状态定义 f [ i , j ] f[i,j] f[i,j]表示对于前 i i i个字符,左括号数量比右括号数量多 j j j个的集合。
那么我们最后的答案就是满足 f [ n , i ] ≠ 0 f[n,i]\neq0 f[n,i]=0的最小的 i i i,输出 f [ n , i ] f[n,i] f[n,i]
状态转移
s [ i ] = ′ ( ′ s[i]='(' s[i]=( f [ i , j ] = f [ i − 1 , j − 1 ] f[i,j]=f[i-1,j-1] f[i,j]=f[i1,j1]
s [ i ] = ′ ) ′ s[i]=')' s[i]=) :对集合进行划分:
添加0个左括号: f [ i ] [ j ] = f [ i − 1 , j + 1 ] f[i][j]=f[i-1,j+1] f[i][j]=f[i1,j+1],当前的右括号抵消了一个变为 j j j个。
添加1个左括号: f [ i ] [ j ] = f [ i − 1 , j ] f[i][j]=f[i-1,j] f[i][j]=f[i1,j],添加的和当前的抵消。
添加2个左括号: f [ i ] [ j ] = f [ i − 1 ] [ j − 1 ] f[i][j]=f[i-1][j-1] f[i][j]=f[i1][j1]

添加 j + 1 j+1 j+1个左括号: f [ i ] [ j ] = f [ i − 1 , 0 ] f[i][j]=f[i-1,0] f[i][j]=f[i1,0]
综上 f [ i ] [ j ] = f [ i − 1 ] [ 0 ] + f [ i − 1 ] [ 1 ] + . . . + f [ i − 1 ] [ j + 1 ] f[i][j]=f[i-1][0]+f[i-1][1]+...+f[i-1][j+1] f[i][j]=f[i1][0]+f[i1][1]+...+f[i1][j+1]
发现 f [ i ] [ j − 1 ] = f [ i − 1 ] [ 0 ] + f [ i − 1 ] [ 1 ] + . . . + f [ i − 1 ] [ j ] f[i][j-1]=f[i-1][0]+f[i-1][1]+...+f[i-1][j] f[i][j1]=f[i1][0]+f[i1][1]+...+f[i1][j]
那么优化后: f [ i ] [ j ] = f [ i ] [ j − 1 ] + f [ i − 1 ] [ j + 1 ] f[i][j]=f[i][j-1]+f[i-1][j+1] f[i][j]=f[i][j1]+f[i1][j+1]

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
typedef long long LL;

const int N = 5100, mod = 1e9 + 7;

LL f[N][N];
int n;
char s[N];

LL solve(){
    memset(f, 0, sizeof f);
    f[0][0] = 1;
    for(int i = 1; i <= n; i++){
        if(s[i] == '(') {
            for(int j = 1; j <= n; j++)
                f[i][j] = f[i - 1][j - 1];
        } else{
            f[i][0] = (f[i - 1][0] + f[i - 1][1]) % mod;
            for(int j = 1; j <= n; j++)
                f[i][j] = (f[i][j - 1] + f[i - 1][j + 1]) % mod;
        }
    }
    for(int i = 0; i <= n; i++)
        if(f[n][i])
            return f[n][i];
    return 0;
}
int main(){
    scanf("%s", s + 1);
    n = strlen(s + 1);
    LL x = solve();
    reverse(s + 1, s + 1 + n);
    for(int i = 1; i <= n; i++)
        if(s[i] == '(') s[i] = ')';
        else s[i] = '(';
    LL y = solve();
    cout << x * y % mod;
    return 0;
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值