动态规划:括号序列

题目大意

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

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

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

解题思路

s t e p 1 step1 step1

我们将左括号的值看作 1 1 1,右括号的值看作 − 1 -1 1

定义 s u m [ i ] sum[i] sum[i] 表示前 i i i 个括号的和, n n n 表示序列的长度,那么对于一个合法序列,其必定满足 ∀ i ∈ ( 1 , n ) , s u m [ i ] ≥ 0 \forall i\in(1,n),sum[i] \geq 0 i(1,n),sum[i]0 s u m [ n ] = 0 sum[n] = 0 sum[n]=0

s t e p 2 step2 step2

我们需要将添加括号使得原括号序列成为合法括号序列。
1、添加的左括号必然只能和原序列中的右括号匹配;
2、添加的右括号必然只能和原序列中的左括号匹配;
3、若添加的左括号和添加的右括号匹配了,则是一对无意义的添加。
   ⟺    \iff
1、添加的左括号的方案只和原序列中的右括号相关;
2、添加的右括号的方案只和原序列中的左括号相关,
3、以左右括号的添加是相互独立的。设左括号的添加方案为 f 1 f1 f1,右括号的添加方案为 f 2 f2 f2,那么显然最后的答案就是 f 1 × f 2 f1\times f2 f1×f2

s t e p 3 step3 step3

先考虑左括号的添加方案数。

设原序列中有 c n t cnt cnt 个右括号,我们以原序列中的右括号为分界点,那么一共可以划分出 c n t + 1 cnt+1 cnt+1 个区域。我们只能在第 1 ∼ c n t 1\sim cnt 1cnt 个区域添加左括号,因为若是在第 c n t + 1 cnt+1 cnt+1 区域添加左括号,将不会有右括号可以与它匹配。

定义 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示前 i i i 个区域(右括号),在我们添加了若干个左括号后一共有 j j j 个左括号的方案数。对于两个不同的方案,它们至少有一个区域添加的左括号的个数不相同,于是可得:

d p [ i ] [ j ] = d p [ i − 1 ] [ 0 ] + d p [ i − 1 ] [ 1 ] + d p [ i − 1 ] [ 2 ] + ⋯ + d p [ i − 1 ] [ j ] dp[i][j] = dp[i - 1][0] + dp[i - 1][1] +\\ dp[i - 1][2] + \cdots+ dp[i - 1][j] dp[i][j]=dp[i1][0]+dp[i1][1]+dp[i1][2]++dp[i1][j]

注意

题干中要求合法序列,所以当右括号的个数为 i i i 时,它前面的左括号的个>数必然大于等于 i i i
我们定义 s u m [ i ] sum[i] sum[i] 表示第 i i i 个右括号之前的左括号个数,当 i > s u m [ i ] i > sum[i] i>sum[i] 时, j j j 必须满足 j ≥ i − s u m [ i ] j \geq i-sum[i] jisum[i]

添加左括号的方案数计算完成了。
对于右括号的添加方案,原括号序列反转,并令 ( → \rightarrow )) → \rightarrow (,然后按照求左括号的方案数的方法再求一遍即可。

优化——前缀和数组
对于 d p [ i − 1 ] [ 0 ] + d p [ i − 1 ] [ 1 ] + d p [ i − 1 ] [ 2 ] + ⋯ + d p [ i − 1 ] [ j ] dp[i - 1][0] + dp[i - 1][1] + dp[i - 1][2] + \cdots+ dp[i - 1][j] dp[i1][0]+dp[i1][1]+dp[i1][2]++dp[i1][j],用别的式子来代替。

d p [ i ] [ j ] = d p [ i − 1 ] [ 0 ] + d p [ i − 1 ] [ 1 ] + ⋯ + d p [ i − 1 ] [ j ] = ( d p [ i − 1 ] [ 0 ] + d p [ i − 1 ] [ 1 ] + ⋯ + d p [ i − 1 ] [ j − 1 ] ) + d p [ i − 1 ] [ j ] = d p [ i ] [ j − 1 ] − d p [ i − 1 ] [ j ] dp[i][j] = dp[i - 1][0] + dp[i - 1][1] + \cdots +dp[i - 1][j] \\ =(dp[i - 1][0] + dp[i - 1][1] + \cdots + dp[i - 1][j-1] ) +dp[i - 1][j] = dp[i][j - 1] - dp[i - 1][j] dp[i][j]=dp[i1][0]+dp[i1][1]++dp[i1][j]=(dp[i1][0]+dp[i1][1]++dp[i1][j1])+dp[i1][j]=dp[i][j1]dp[i1][j]
j ∈ ( i − s u m [ i ] , c n t ) j\in(i-sum[i] , cnt) j(isum[i],cnt)
去掉一层循环,复杂度为 O ( n 2 ) O(n^2) O(n2)

仅当 j ∈ [ ( i − s u m [ i ] ) ∼ c n t ] j \in [(i-sum[i])\sim cnt] j[(isum[i])cnt] 时才满足 d p [ i ] [ j ] = ∑ k = 0 j d p [ i − 1 ] [ k ] dp[i][j] = \sum \limits_{k=0}^{j} dp[i - 1][k] dp[i][j]=k=0jdp[i1][k],所以 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i ] [ j − 1 ] dp[i][j] = dp[i-1][j] + dp[i][j - 1] dp[i][j]=dp[i1][j]+dp[i][j1] 的转移并不完全合法。

用类似的方法来优化:

  • 定义 p r e [ j ] pre[j] pre[j] 表示 ∑ k = 1 j d p [ i − 1 ] [ k ] \sum\limits _{k=1}^{j}dp[i-1][k] k=1jdp[i1][k]
  • j ∈ [ 0 ∼ ( i − s u m [ i ] ) ] j \in[0\sim (i-sum[i])] j[0(isum[i])] 时, p r e [ j ] = 0 pre[j] = 0 pre[j]=0
  • j ∈ [ ( i − s u m [ i ] ) ∼ c n t ] j \in [(i-sum[i])\sim cnt] j[(isum[i])cnt] 时, p r e [ j ] = p r e [ j − 1 ] + d p [ i − 1 ] [ j ] pre[j] = pre[j - 1] + dp[i-1][j] pre[j]=pre[j1]+dp[i1][j]
  • 那么当 j ∈ [ ( i − s u m [ i ] ) ∼ c n t ] j \in [(i-sum[i])\sim cnt] j[(isum[i])cnt] d p [ i ] [ j ] = p r e [ j ] dp[i][j] = pre[j] dp[i][j]=pre[j]

这样时间复杂度就优化到 O ( n 2 ) O(n^2) O(n2) 了。

AC_Code

Python3


# -*- coding: utf-8 -*-
# @Author : BYW-yuwei
# @Software: python3.8.6

MOD=int(1e9+7)

def calc(cnt,flag,n):
    global MOD
    if cnt==0:return 1
    dp=[[0]*(cnt+2) for i in range(n+5)]
    sum_res=[0]*(n+5)
    pre=[0]*(cnt+2) ; nex=[0]*(cnt+2)
    if flag==False:
        revstr=bracket_list[::-1]
        for i in range(0,n):
            if revstr[i]==')':bracket_list[i]='('
            else:bracket_list[i]=')'
    m,now=0,0
    for i in range(0,n):
        if bracket_list[i]==')':
            sum_res[m+1]=now
            m+=1
        if bracket_list[i]=='(':
            now+=1
    if sum_res[1]>0:
        dp[1][0]=1 ; pre[0]=1
    for i in range(1,cnt+1):
        dp[1][i]=1
        pre[i]=pre[i-1]+1
    for i in range(2,m+1):
        dp[i]=pre
        for j in range(0,cnt+1):nex[j]=0
        for j in range(max(0,i-sum_res[i]),cnt+1):
            nex[j]=(nex[j-1]+dp[i][j])%MOD
        pre=nex[:]
    return dp[m][cnt]


if __name__=="__main__":
    bracket_list=[i for i in input()]
    n=len(bracket_list)
    tot,cnt1,cnt2=0,0,0

    for bracket in bracket_list:
        if bracket=='(':tot+=1
        else:tot-=1
        if tot<0:
            cnt1+=1
            tot=0
    cnt2=tot
    res=calc(cnt1,1,n)*calc(cnt2,0,n)%MOD
    print(res)
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是雨玮a

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值