定义如下正规括号序列(字符串):
- 空序列是正规括号序列。
- 如果S是正规括号序列,那么(S)和[S]也是正规括号序列。
- 如果A和B都是正规括号序列,那么AB也是正规括号序列。
例如,下面的字符串都是正规括号序列:(),[],(()),([]),()[],()[()],而如下字符串则不是正规括号序列:(,[,],)(,([()。输入一个长度不超过100的,由“(”、“)”、“[”、“]”构成的序列,添加尽量少的括号,得到一个规则序列。如有多解,输出任意一个序列即可。
【分析】
设串S至少需要增加d(S)个括号,转移如下:
- 如果S形如(S′)或者[S′],转移到d(S′)。
- 如果S至少有两个字符,则可以分成AB,转移到d(A)+d(B)。
边界是:S为空时d(S)=0,S为单字符时d(S)=1。注意(S′, [S′, ) S′之类全部属于第二种转移,不需要单独处理。注意:不管S是否满足第一条,都要尝试第二种转移,否则“[][]”会转移到“][”,然后就只能加两个括号了。当然,上述“方程”只是概念上的,落实到程序时要改成子串在原串中的起始点下标,即用d(i,j)表示子串S[i~j]至少需要添加几个括号。本题需要打印解,如何打印解呢?可以在打印时重新检查一下哪个决策最好。这样做的好处是节约空间,坏处是打印时代码较复杂,速度稍慢,但是基本上可以忽略不计(因为只有少数状态需要打印)。
本题唯一的陷阱是:输入串可能是空串,因此不能用scanf("%s", s)的方式输入,只能用getchar、fgets或者getline。
// 算法:形如(S)或者[S],转移到d(S),然后分成AB,转移到d(A)+d(B)。注意(S, [S, )S之类全部属于第二种转移。
// 注意输入有空行。
// 本程序是递推解法
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 100 + 5;
char S[maxn];
int n, d[maxn][maxn];
bool match(char a, char b) {
return (a == '(' && b == ')') || (a == '[' && b == ']');
}
void dp() {
for(int i = 0; i < n; i++) {
d[i+1][i] = 0;
d[i][i] = 1;
}
for(int i = n-2; i >= 0; i--)
for(int j = i+1; j < n; j++) {//每次s[i]和后面的s[j]匹配更新
d[i][j] = n;
if(match(S[i], S[j])) d[i][j] = min(d[i][j], d[i+1][j-1]);
for(int k = i; k < j; k++)
d[i][j] = min(d[i][j], d[i][k] + d[k+1][j]);
}
}
void print(int i, int j) {
if(i > j) return ;
if(i == j) {
if(S[i] == '(' || S[i] == ')') printf("()");
else printf("[]");
return;
}
int ans = d[i][j];
if(match(S[i], S[j]) && ans == d[i+1][j-1]) {
printf("%c", S[i]); print(i+1, j-1); printf("%c", S[j]);
return;
}
for(int k = i; k < j; k++)
if(ans == d[i][k] + d[k+1][j]) {
print(i, k); print(k+1, j);
return;
}
}
void readline(char* S) {
fgets(S, maxn, stdin);
}
int main() {
int T;
readline(S);
sscanf(S, "%d", &T);
readline(S);
while(T--) {
readline(S);
n = strlen(S) - 1;
memset(d, -1, sizeof(d));
dp();
print(0, n-1);
printf("\n");
if(T) printf("\n");
readline(S);
}
return 0;
}