题目大意
给定一个括号序列,要求尽可能少地添加若干括号使得括号序列变得合法,当添加完成后,会产生不同的添加结果,请问有多少种本质不同的添加结果。
两个结果是本质不同的是指存在某个位置一个结果是左括号,而另一个是右括号。
例如,对于括号序列 ( ( ( ) ((() (((),只需要添加两个括号就能让其合法,有以下几种不同的添加结果: ( ) ( ) ( ) ()()() ()()()、 ( ) ( ( ) ) ()(()) ()(())、 ( ( ) ) ( ) (())() (())()、 ( ( ) ( ) ) (()()) (()()) 和 ( ( ( ) ) ) ((())) ((()))。
解题思路
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 1∼cnt 个区域添加左括号,因为若是在第 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[i−1][0]+dp[i−1][1]+dp[i−1][2]+⋯+dp[i−1][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] j≥i−sum[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[i−1][0]+dp[i−1][1]+dp[i−1][2]+⋯+dp[i−1][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[i−1][0]+dp[i−1][1]+⋯+dp[i−1][j]=(dp[i−1][0]+dp[i−1][1]+⋯+dp[i−1][j−1])+dp[i−1][j]=dp[i][j−1]−dp[i−1][j]。
j ∈ ( i − s u m [ i ] , c n t ) j\in(i-sum[i] , cnt) j∈(i−sum[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∈[(i−sum[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=0∑jdp[i−1][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[i−1][j]+dp[i][j−1] 的转移并不完全合法。
用类似的方法来优化:
- 定义 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=1∑jdp[i−1][k]。
- 当 j ∈ [ 0 ∼ ( i − s u m [ i ] ) ] j \in[0\sim (i-sum[i])] j∈[0∼(i−sum[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∈[(i−sum[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[j−1]+dp[i−1][j]。
- 那么当 j ∈ [ ( i − s u m [ i ] ) ∼ c n t ] j \in [(i-sum[i])\sim cnt] j∈[(i−sum[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.6MOD=int(1e9+7)def calc(cnt,flag,n):global MODif cnt==0:return 1dp=[[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,0for i in range(0,n):if bracket_list[i]==')':sum_res[m+1]=nowm+=1if bracket_list[i]=='(':now+=1if sum_res[1]>0:dp[1][0]=1 ; pre[0]=1for i in range(1,cnt+1):dp[1][i]=1pre[i]=pre[i-1]+1for i in range(2,m+1):dp[i]=prefor j in range(0,cnt+1):nex[j]=0for j in range(max(0,i-sum_res[i]),cnt+1):nex[j]=(nex[j-1]+dp[i][j])%MODpre=nex[:]return dp[m][cnt]if __name__=="__main__":bracket_list=[i for i in input()]n=len(bracket_list)tot,cnt1,cnt2=0,0,0for bracket in bracket_list:if bracket=='(':tot+=1else:tot-=1if tot<0:cnt1+=1tot=0cnt2=totres=calc(cnt1,1,n)*calc(cnt2,0,n)%MODprint(res)