首先考虑下面的问题:Code[VS] 3657
我们用以下规则定义一个合法的括号序列:
(1)空序列是合法的
(2)假如S是一个合法的序列,则 (S) 和[S]都是合法的
(3)假如A 和 B 都是合法的,那么AB和BA也是合法的
例如以下是合法的括号序列:
(), [], (()), ([]), ()[], ()[()]
以下是不合法括号序列的:
(, [, ], )(, ([]), ([()
现在给定一些由'(', ')', '[', ,']'构成的序列 ,请添加尽量少的括号,得到一个合法的括号序列。
输入包括号序列S。含最多100个字符(四种字符: '(', ')', '[' and ']') ,都放在一行,中间没有其他多余字符。
使括号序列S成为合法序列需要添加最少的括号数量。
样例输入 Sample Input
([()
样例输出 Sample Output
2
这是LRJ黑书上讲动态规划的第一道例题,我觉得不算简单>_<.,但让我明白了动态规划的两种动机:记忆化搜索和自底向上的递推。先说这道题的理解,黑书上设SiSi+1...Sj最少需要添加d[i,j]个括号。当S是'(S)'或'[S]'形式是很好理解,由于两端是对称的,那么我可以递归考虑里面的:d[i,j]=d[i+1,j-1]。当S是'(S'、'[S'、'S)'、'S]'等类似前面的道理,只不过考虑的分别是d[i,j]=d[i+1,j],d[i,j]=d[i,j-1]。其实让我迷惑的是最后还要把S分成两段Si....Sk,Sk+1...Sj分别求解再相加。后来看了别人博客的解释才明白些。因为S就可以只看成两类,两段匹配的和不匹配的,匹配的直接递归,不匹配的就分成两部分再求解。
所以针对上面的问题,就有了两种dp写法。dp[i][j]表示i、j为开头结尾时需要添加的括号数。
记忆化搜索:参考代码如下。黑书里面写的有我注释那那部分,但是按照上面的分析,其实直接分成dp[i][j]=dp[i+1][j-1]和dp[i][j]=dp[i][k]+dp[k+1][j]就可以了。但是我觉得那部分代码对我理解递归还是个很有帮助的,而且不注释好像更快些。Recursive is amazing!
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6 const int MAXN = 107; 7 int dp[MAXN][MAXN]; 8 char s[MAXN]; 9 10 int dfs(int i, int j) 11 { 12 if (dp[i][j] != -1) return dp[i][j]; 13 if (i > j) return 0; 14 if (i == j) return 1; 15 int ans = 1e9; 16 if ((s[i] == '('&&s[j] == ')') || (s[i] == '['&&s[j] == ']')) 17 ans = min(ans, dfs(i + 1, j - 1)); 18 /*else if (s[i] == '(' || s[i] == '[') 19 ans = min(ans, dfs(i + 1, j) + 1); 20 else if (s[j] == ')' || s[j] == ']') 21 ans = min(ans, dfs(i, j - 1) + 1);*/ 22 for (int k = i; k < j; k++) 23 ans = min(ans, dfs(i, k) + dfs(k + 1, j)); 24 return dp[i][j] = ans; 25 } 26 27 int main() 28 { 29 while (scanf("%s",s)==1) 30 { 31 int len = strlen(s); 32 memset(dp, -1, sizeof(dp)); 33 printf("%d ", dfs(0, len - 1)); 34 } 35 }
自底向上的递推:其实看起来代码和上面的差不多。也是一样,去掉注释竟然会快些。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6 const int MAXN = 107; 7 int dp[MAXN][MAXN]; 8 char s[MAXN]; 9 10 int main() 11 { 12 while (scanf("%s",s)==1) 13 { 14 int len = strlen(s); 15 for (int i = 0; i < len; i++) { 16 dp[i][i] = 1, dp[i][i - 1] = 0; 17 } 18 for (int p = 1; p < len; p++)//p指的是i、j之间的距离 19 { 20 for (int i = 0; i + p < len; i++) 21 { 22 int j = i + p; 23 dp[i][j] = 1e9; 24 if ((s[i] == '('&&s[j] == ')') || (s[i] == '['&&s[j] == ']')) 25 dp[i][j] = dp[i + 1][j - 1]; 26 /* else if (s[i] == '(' || s[i] == '[') 27 dp[i][j] = min(dp[i][j], dp[i + 1][j])+1; 28 else if (s[j] == ')' || s[j] == ']') 29 dp[i][j] = min(dp[i][j], dp[i][j - 1])+1; */ 30 for (int k = i; k < j; k++) 31 dp[i][j] = min(dp[i][j], dp[i][k]+dp[k+1][j]); 32 } 33 } 34 printf("%d ", dp[0][len - 1]); 35 } 36 return 0; 37 }
下面的UVa 1626 poj 1141就是比上面的题多了个输出结果,这俩题一样,就是输入输出要求有点差别而已。需要特别注意的是,这两道题输入中都有空格,所以只能用gets()函数,我用scanf("%s")WA到死。。。(也没见题中说由空格啊!有空格还对吗?难道空格是在字符串开头?)
其实一看见让输出我是很懵逼的,这怎么输出。能求出最少添加数我就很开心了。下面说说自己的理解,别人的方法是递归输出。既然是递归输出,就先考虑一下边间,显然i>j时直接return.i==j时,是'('或')'输出'()'否则输出'[]',当i!=j时,若i,j两端点正好匹配,那就先输出左端点再递归输出i+1,j-1部分最后输出又端点,若是剩下的其他情况就像上面一样分成两部分判断继续递归。这里分成两部分后应该在哪里分开递归?自然是在更新dp[i][j]=dp[i][k]+dp[k+1][j]的地方,网上有人在dp时添加了另外一个数组记录这个位置,也有人没有添加,而是递归输出结果的时候再判断,我这里选择了第二种,代码看起来简洁些。
自底向上递推:为了方便把端点匹配的情况写成了一个函数。有一个点就是注释里的dp[i][j]==dp[i+1][j-1]不能少,否则会WA!感觉应该是虽然有可能i,j匹配,但这不是原序列中i、j对应的匹配,因为这时候是在递归,所以不加上会WA。估计我比赛的时候不会注意到这种细节。。。。但另开个数组记录就不用考虑了。
UVa 1626
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6 const int MAXN=107; 7 int dp[MAXN][MAXN]; 8 char s[MAXN]; 9 10 bool Judge(int i,int j) 11 { 12 if(s[i]=='('&&s[j]==')') return 1; 13 if(s[i]=='['&&s[j]==']') return 1; 14 return 0; 15 } 16 17 void Print(int i,int j) 18 { 19 if(i>j) return; 20 if(i==j){ 21 if(s[i]=='('||s[j]==')') printf("()"); 22 else printf("[]"); 23 return; 24 }else if(Judge(i,j)&&dp[i][j]==dp[i+1][j-1]){//后面的判断条件不能省略 25 printf("%c",s[i]); 26 Print(i+1,j-1); 27 printf("%c",s[j]); 28 return; 29 }else for(int k=i;k<j;k++) 30 if(dp[i][j]==dp[i][k]+dp[k+1][j]){ 31 Print(i,k); 32 Print(k+1,j); 33 return; 34 } 35 } 36 37 int main() 38 { 39 int T; 40 scanf("%d",&T); 41 getchar(); 42 while(T--) 43 { 44 gets(s); 45 gets(s); 46 int len=strlen(s); 47 memset(dp,0,sizeof(dp)); 48 for(int i=0;i<len;i++){ 49 dp[i][i]=1,dp[i][i-1]=0; 50 } 51 for(int p=1;p<len;p++) 52 { 53 for(int i=0;i+p<len;i++) 54 { 55 int j=i+p; 56 dp[i][j]=1e9; 57 if(Judge(i,j)) 58 dp[i][j]=dp[i+1][j-1]; 59 for(int k=i;k<j;k++) 60 dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]); 61 } 62 } 63 Print(0,len-1); 64 printf(" "); 65 if(T) 66 printf(" "); 67 } 68 return 0; 69 }
附带一个用flag[]数组标记的写法:
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6 const int MAXN=107; 7 int dp[MAXN][MAXN],flag[MAXN][MAXN]; 8 char s[MAXN]; 9 10 bool Judge(int i,int j) 11 { 12 if(s[i]=='('&&s[j]==')') return 1; 13 if(s[i]=='['&&s[j]==']') return 1; 14 return 0; 15 } 16 17 void Print(int i,int j) 18 { 19 if(i>j) return; 20 if(i==j){ 21 if(s[i]=='('||s[j]==')') printf("()"); 22 else printf("[]"); 23 return; 24 }else if(flag[i][j]==-1){ 25 printf("%c",s[i]); 26 Print(i+1,j-1); 27 printf("%c",s[j]); 28 return; 29 }else { 30 Print(i,flag[i][j]); 31 Print(flag[i][j]+1,j); 32 } 33 } 34 35 int main() 36 { 37 int T; 38 scanf("%d",&T); 39 getchar(); 40 while(T--) 41 { 42 gets(s); 43 gets(s); 44 int len=strlen(s); 45 memset(dp,0,sizeof(dp)); 46 for(int i=0;i<len;i++){ 47 dp[i][i]=1,dp[i][i-1]=0; 48 } 49 for(int p=1;p<len;p++) 50 { 51 for(int i=0;i+p<len;i++) 52 { 53 int j=i+p; 54 dp[i][j]=1e9; 55 if(Judge(i,j)){ 56 dp[i][j]=dp[i+1][j-1]; 57 flag[i][j]=-1; 58 } 59 for(int k=i;k<j;k++){ 60 if(dp[i][j]>dp[i][k]+dp[k+1][j]){ 61 dp[i][j]=dp[i][k]+dp[k+1][j]; 62 flag[i][j]=k; 63 } 64 } 65 } 66 } 67 Print(0,len-1); 68 printf(" "); 69 if(T) 70 printf(" "); 71 } 72 return 0; 73 }
poj 1141类似:
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6 const int MAXN = 107; 7 int dp[MAXN][MAXN]; 8 char s[MAXN]; 9 10 bool Judge(int i, int j) 11 { 12 if (s[i] == '('&&s[j] == ')') return true; 13 if (s[i] == '['&&s[j] == ']') return true; 14 return false; 15 } 16 17 void Print(int l, int r) 18 { 19 if (l > r) return; 20 if (l == r) { 21 if (s[l] == '(' || s[r] == ')') 22 printf("()"); 23 else printf("[]"); 24 return; 25 } 26 else if (Judge(l, r) && dp[l][r] == dp[l + 1][r - 1]) { 27 printf("%c", s[l]); 28 Print(l + 1, r - 1); 29 printf("%c", s[r]); 30 }else for(int k=l;k<r;k++) 31 if (dp[l][r] == dp[l][k] + dp[k + 1][r]) { 32 Print(l, k); 33 Print(k + 1, r); 34 break; 35 } 36 } 37 38 int main() 39 { 40 while (gets(s)) 41 { 42 int len = strlen(s); 43 memset(dp, 0, sizeof(dp)); 44 for (int i = 0; i < len; i++) { 45 dp[i][i - 1] = 0, dp[i][i] = 1; 46 } 47 for (int p = 1; p < len; p++) 48 { 49 for (int i = 0; i < len - p; i++) { 50 int j = i + p; 51 dp[i][j] = 1e9; 52 if ((s[i] == '('&&s[j] == ')') || (s[i] == '['&&s[j] == ']')) 53 dp[i][j] = dp[i + 1][j - 1]; 54 for (int k = i; k < j; k++) 55 dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j]); 56 } 57 } 58 Print(0, len - 1); 59 printf(" "); 60 } 61 return 0; 62 }
记忆化搜索也是类似的方法,比递推满了好多倍。。用flag[]数组标记比较好,不标记不知道怎么弄>_<。而且要像上面一样令int ans=1e9,最后再返回dp[i][j]=ans,直接令dp[i][j]=1e9会出错。。。先不深究了,这题写了一天。。。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6 const int MAXN = 107; 7 int dp[MAXN][MAXN],flag[MAXN][MAXN]; 8 char s[MAXN]; 9 10 bool Judge(int i, int j) 11 { 12 if (s[i] == '('&&s[j] == ')') return 1; 13 if (s[i] == '['&&s[j] == ']') return 1; 14 return 0; 15 } 16 17 int dfs(int i, int j) 18 { 19 if (dp[i][j] != -1) return dp[i][j]; 20 if (i > j) return 0; 21 if (i == j) return 1; 22 int ans = 1e9; 23 if (Judge(i,j)) { 24 ans = min(ans, dfs(i + 1, j - 1)); 25 flag[i][j] = -1; 26 } 27 for (int k = i; k < j; k++) { 28 if (ans > dfs(i, k) + dfs(k + 1, j)) { 29 ans = dfs(i, k) + dfs(k + 1, j); 30 flag[i][j] = k; 31 } 32 } 33 return dp[i][j] = ans; 34 } 35 36 void Print(int i, int j) 37 { 38 if (i>j) return; 39 if (i == j) { 40 if (s[i] == '(' || s[j] == ')') printf("()"); 41 else printf("[]"); 42 return; 43 } 44 else if (flag[i][j] == -1) { 45 printf("%c", s[i]); 46 Print(i + 1, j - 1); 47 printf("%c", s[j]); 48 return; 49 } 50 else { 51 Print(i, flag[i][j]); 52 Print(flag[i][j] + 1, j); 53 } 54 } 55 56 int main() 57 { 58 int T; 59 scanf("%d", &T); 60 getchar(); 61 while (T--) 62 { 63 gets(s); 64 gets(s); 65 int len = strlen(s); 66 memset(dp, -1, sizeof(dp)); 67 dfs(0, len - 1); 68 Print(0, len - 1); 69 printf(" "); 70 if (T) 71 printf(" "); 72 } 73 return 0; 74 }
poj 1141的完全类似。。。