题目大意
给定一个括号序列,要求尽可能少地添加若干括号使得括号序列变得合法,当添加完成后,会产生不同的添加结果,请问有多少种本质不同的添加结果。
两个结果是本质不同的是指存在某个位置一个结果是左括号,而另一个是右括号。
例如,对于括号序列 ( ( ( ) ((() (((),只需要添加两个括号就能让其合法,有以下几种不同的添加结果: ( ) ( ) ( ) ()()() ()()()、 ( ) ( ( ) ) ()(()) ()(())、 ( ( ) ) ( ) (())() (())()、 ( ( ) ( ) ) (()()) (()()) 和 ( ( ( ) ) ) ((())) ((()))。
解题思路
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.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)