NYOJ15|括号匹配(二)|区间DP|Elena
括号匹配(二)
- 描述
- 给你一个字符串,里面只包含"(",")","[","]"四种符号,请问你需要至少添加多少个括号才能使这些括号匹配起来。
- 如:
[]是匹配的
([])[]是匹配的
((]是不匹配的
([)]是不匹配的
- 输入
- 第一行输入一个正整数N,表示测试数据组数(N<=10)
每组测试数据都只有一行,是一个字符串S,S中只包含以上所说的四种字符,S的长度不超过100 - 输出
- 对于每组测试数据都输出一个正整数,表示最少需要添加的括号的数量。每组测试输出占一行
- 样例输入
-
4 [] ([])[] ((] ([)]
- 样例输出
-
0 0 3 2
对于这道题,我还是跟②2017=5.13-7.1中的合并石子一样,贴出两份不同的类型的题解以及解释。
思路其实差不多,是枚举过程有些差异,分给两种不同习惯的人。
DP[i][j]指i到j中合法括号配对数。比如对于字符串(),DP[1][2]=2。SP[]用于存储括号字符串,我是从下标1存储起的。N存储字符串的长度。
我们可以这样想,DP[i][j]合法的情况只有以下三种:
1.SP[i]=='('&&SP[j]==')'且DP[i+1][j-1]合法。
2.SP[i]=='['&&SP[j]==']'且DP[i+1][j-1]合法。3.能够切成两段合法的括号序列。
我们可以计算出合法的括号配对数,然后用字符串长度减去它,就是答案。这里合法的括号配对数指:比如对于字符串(),DP[1][2]=2,则合法括号配对数指2。第一种题解:
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #include<cstdlib> 5 using namespace std; 6 int T,DP[101][101],N; 7 char SP[101]; 8 int main() 9 { 10 scanf("%d",&T); 11 while (T--) { 12 N=0; 13 scanf("%s",SP+1); 14 while (SP[N+1]) N++; 15 memset(DP,0,sizeof(DP)); 16 for(int i=N-1; i>=1; i--) 17 for(int j=i+1; j<=N; j++) { 18 if ((SP[i]=='('&&SP[j]==')')||(SP[i]=='['&&SP[j]==']')) 19 DP[i][j]=DP[i+1][j-1]+2; 20 for (int k=i; k<j; k++) 21 DP[i][j]=max(DP[i][j],DP[i][k]+DP[k+1][j]); 22 } 23 printf("%d\n",N-DP[1][N]); 24 } 25 return 0; 26 }
我仍然挑出部分代码讲解:
memset(DP,0,sizeof(DP));//因为有T组数据,所以对于每一组数据我们都需要初始化DP数组。
for(int i=N-1; i>=1; i--)
for(int j=i+1; j<=N; j++) {
if ((SP[i]=='('&&SP[j]==')')||(SP[i]=='['&&SP[j]==']'))//第1、2种合法情况的判断。
DP[i][j]=DP[i+1][j-1]+2;//括号有两边,所以+2;
for (int k=i; k<j; k++)
DP[i][j]=max(DP[i][j],DP[i][k]+DP[k+1][j]);//对于第三种情况的判断。
}
printf("%d\n",N-DP[1][N]);肯定有人要问:为什么DP[i][j]=max(DP[i][j],DP[i][k]+DP[k+1][j]);就可以计算出如([])()这样的情况的答案呢?
是这样的,我们可以这么想:假如k枚举到了3,那么DP[1][3]就是2,DP[4][6]也是2,DP[1][6]答案就是4。
假如k枚举到了4,那么DP[1][4]答案就是4,DP[5][6]答案就是2,DP[1][6]的答案就是6,大于原来的答案且合法,于是更新。
这样就可以完美地从括号中间分割出来啦~因为如果()()是类似这样的合法括号序列,从中间分割出来的答案一定是最优的,所以就可以保证答案的正确性啦!
这种写法的原理是:当i相同时,j小的先算;当i不相同时,i大的先算。
比如:DP[i][k]比DP[i][j]先算,因为i=i,k比较小;DP[k+1][j]比DP[i][j]先算,因为k+1不等于i,k+1>i。
这样就可以保证循环先后的正确性,也就是保证我们计算这一步需要用的东西在计算这一步之前就计算好了。
第二种写法:
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #include<cstdlib> 5 using namespace std; 6 int T,DP[101][101],N; 7 char SP[101]; 8 int main() 9 { 10 scanf("%d",&T); 11 while (T--) { 12 N=0; 13 scanf("%s",SP+1); 14 while (SP[N+1]) N++; 15 memset(DP,0,sizeof(DP)); 16 for (int LEN=2; LEN<=N; LEN++) 17 for (int i=1,j=LEN; j<=N; i++,j++) { 18 if ((SP[i]=='(' && SP[j]==')')||(SP[i]=='['&&SP[j]==']')) 19 DP[i][j]=DP[i+1][j-1]+2; 20 for (int k=i; k<j; k++) 21 DP[i][j]=max(DP[i][k]+DP[k+1][j],DP[i][j]); 22 } 23 printf("%d\n",N-DP[1][N]); 24 } 25 return 0; 26 }
这种写法其实本质上和第一种是一样的,只是枚举的方式略有不同,是通过枚举序列长度来保证DP循环的先后性的,所以放出来给熟悉这种写法的人看。
memset(DP,0,sizeof(DP));//初始化
for (int LEN=2; LEN<=N; LEN++) //枚举序列长度
for (int i=1,j=LEN; j<=N; i++,j++) {
if ((SP[i]=='(' && SP[j]==')')||(SP[i]=='['&&SP[j]==']'))//第1、2种合法情况的判断。
DP[i][j]=DP[i+1][j-1]+2;//括号有两边,所以+2;
for (int k=i; k<j; k++)
DP[i][j]=max(DP[i][k]+DP[k+1][j],DP[i][j]);//对于第三种情况的判断。
}
printf("%d\n",N-DP[1][N]);对于第三种情况的判断上面讲的很清楚了。
有问题可以直接在评论里面提问,有需要转载的请得到我的允许,否则按侵权处理。
Elena loves NiroBC forever!