题目
试题 J: 括号序列
时间限制: 5.0s 内存限制: 512.0MB 本题总分:25 分
【问题描述】
给定一个括号序列,要求尽可能少地添加若干括号使得括号序列变得合法,
当添加完成后,会产生不同的添加结果,请问有多少种本质不同的添加结果。
两个结果是本质不同的是指存在某个位置一个结果是左括号,而另一个是右括
号。
例如,对于括号序列 (((),只需要添加两个括号就能让其合法,有以下几
种不同的添加结果:()()()、()(())、(())()、(()()) 和 ((()))。
【输入格式】
输入一行包含一个字符串 s,表示给定的括号序列,序列中只有左括号和
右括号。
【输出格式】
输出一个整数表示答案,答案可能很大,请输出答案除以 1000000007 (即
10 9 + 7) 的余数。
【样例输入】
((()
【样例输出】
5
【评测用例规模与约定】
对于 40% 的评测用例,|s| ≤ 200。
对于所有评测用例,1 ≤ |s| ≤ 5000。
思路:
首先这个序列中我们需要添加的是左括号和右括号, 那么显然的我们要想左括号和右括号的添加是不是相互独立的呢?答案是肯定的:
考虑到我们只能在空隙中插入括号, 如果我们添加的一对左右括号不是在同一个空隙中, 那么他们显然是互不干扰的;如果是添加在同一个空隙中, 那么他们的添加顺序是唯一的, 只能是)(, 因为如果是()的话, 那我们本次的添加就是无效的, 不满足添加最少的括号使得序列得到匹配。 由此可得, 我们只需要单独计算出添加左括号的方案数, 乘上单独添加右括号的方案数就是答案的数量。
明确了上面那个问题,我们就可以对左右括号进行单独计算了, 我们这里以添加左括号为例。
我们如果以右括号为端点, 将整个序列进行分割, 那么在分割后的每一小段添加左括号的方案数显然只和这段序列中左括号的数量有关, 因为这段序列里全是左括号, 怎么排列都是一种。所以我们只关注左括号的个数就好了, 更准确的来说, 我们只要关注我们添加的左括号的个数。
那么我们可以设计一个状态f[i][j]表示当前枚举到第i个右括号, 我们添加了j个左括号的==合法==方案(注意, 如果我们添加的操作使得前i个右括号都被匹配完后还有剩余的左括号, 我们仍认为这个状态是合法的), 那么我们首先预处理出每个右括号前面至少需要添加的左括号的数量记为add数组,那么显然小于add的方案都是不合法的, 对于大于add的数量, 我们将添加的左括号分为两组, 一组在第i - 1个右括号的前面, 另一组在第i - 1个括号到第i个括号之间, 那么枚举任意一段的数目就可以实现转移了
for(int i = add[1]; i <= len; i ++) f[1][i] = 1;//预处理, 很显然
for(int i = 2; i <= num; i ++)
for(int j = add[i]; j <= len; j ++)//注意从add[i]开始, 比add[i]小的状态一定不合法
for(int k = 0; k <= j; k ++)//k表示的是i - 1到i段添加左括号的数量
f[i][j] = (f[i][j] + f[i - 1][j - k]) % mod;
作者:Alkaid1506
链接:https://www.acwing.com/solution/content/47526/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
这样我们的工作看似就完成了, 但是这个dp的时间复杂度是==n ^ 3==的, 过不了本题5k的数据, 那么我们就要考虑进行优化
我们可以明显注意到
f[i][j] = 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 - 1][j + 1]
那么我们可以得出
f[i][j] = f[i][j - 1] + f[i - 1][j - 1]
那么我们只需要先O(n)的算出f[i][add[i]], 后面的f[i][j]就都可以O(1)转移出了, 总体时间复杂度==n ^ 2==, 可以通过本题
当然对与添加右括号来说, 只需要将序列镜像翻转, 然后当作匹配左括号就可以了
代码:
public class Test {
static int mod = 1000000000+7;
static int []add = new int[5003];
static int [][]f = new int[5003][5003];
static int ans = 0;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
//输入括号字符串
String s =sc.next();
sc.close();
int len =s.length();
char []arr = s.toCharArray();
ans = work(len,arr);
//获取镜像
for(int i =1;i<=len;++i){
if(arr[i-1]=='(') arr[i-1]=')';
else arr[i-1]='(';
}
String s1 = new String(arr);
//字符串反转
String s2 =new StringBuffer(s1).reverse().toString();
ans=ans*work(len,s2.toCharArray())%mod;
System.out.println(ans);
}
private static int work(int len,char []arr){
int lcnt =0,rcnt =0,num=0;//未匹配的左右括号数,及右括号编号
for(int i=1;i<=len;++i){
if(arr[i-1]=='('){
lcnt++;
}else{
rcnt++;
num++;
if(lcnt!=0) {
rcnt--;
lcnt--;
}
add[num]=rcnt; //记录最少需要添加左括号的数量,add单调不减
}
}
for(int i =add[1];i<=len;++i){
f[1][i]=1;
}
//n^3转移
// for(int i=2;i<=num;i++){
// for(int j=add[i];j<=len;++j){
// for(int k=0;k<=j;++k){
// f[i][j]=(f[i][j]+f[i-1][j-k])%mod;
//
// }
// }
// }
for(int i=2;i<=num;++i){
for(int j=0;j<=add[i];j++){
f[i][add[i]]=(f[i][add[i]]+f[i-1][j])%mod;
}
for(int j=add[i]+1;j<=len;++j){
f[i][j]=(f[i][j-1]+f[i-1][j])%mod;
}
}
return f[num][rcnt];//返回答案
}
}