本篇为刘汝佳《算法竞赛入门经典(第2版)》P278 的读书笔记。
解题思路:
我们用 (dp(x)(y)) 来记录字符串 (s) 中从位置 (x) 到位置 (y) 需要添加的字符数(其实就是这一段字符串的 “字符失配数”)。
DP部分:
状态转移方程为 (dp(i)(j) = min{dp(i)(k) + dp(k+1)(j) mid i le k < j }),如果字符 (s[i]、s[j]) 相匹配,那么在进行了上述的转移以后也不能忘了 (dp(i+1)(j-1)) 这种情况,即 (dp(i)(j) = min{dp(i)(j), dp(i+1)(j-1)}),因为它的首末字母已经确定匹配了,那么 “失配数” 就看中间的那部分了。如果考虑的这段字符串的首末字母匹配了,是不是就不用进行前面的 (dp(i)(j) = min{dp(i)(k) + dp(k+1)(j) mid i le k < j }) 这一段状态转移了呢?不是的。举个反例,如果我们不进行第一种转移,那么 “[][]" 就会直接转移到 “][”,然后就得加2个括号了。
具体实现方法是外层循环从短到长遍历各种字符串长度,第二层循环遍历首位(末位随之确定),最内层循环遍历字符串中的各位元素。
输出部分:
本题的难点之所在。书中介绍了一种有点像分治法的方法,在打印时对照着我们得出的 (dp) 数组重新检查一下哪个决策最好。当打印 (i) 到 (j) 这部分的字符串的时候,从头到尾遍历字符串,当找到位置 (k) 使得 (dp(i)(k) + dp(k+1)(j) == dp(i)(j)),便可退出循环,将 (print(i,j)) 分成 (print(i,k)) 和 (print(k+1,j));当然,如果字符串首末匹配并且 (dp(i)(j) == dp(i+1)(j-1)),那么问题就变成打印首末字母,在中间考虑 (print(i+1,j-1))。边界情况:(i>j) 时说明上一层刚好解决了类似 “[]" 或者 "()" 的情况,于是我们什么都不用输出;(i==j) 时,我们就要给字符串加匹配字符了,这些都是匹配剩下的字符。
danger!!! 这题的输入输出格式是个坑!请读者自行体会。
AC代码:
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 5 using namespace std; 6 const int maxn=110, inf=0x7fffffff; 7 char inp[maxn]; 8 int dp[maxn][maxn]; 9 bool match(char a,char b){ 10 if(a=='('&&b==')') return true; 11 if(a=='['&&b==']') return true; 12 return false; 13 } 14 void print(int x,int y){ 15 if(x>y) return; 16 if(x==y){ 17 if(inp[x]=='('||inp[x]==')') printf("()"); 18 else printf("[]"); 19 return; 20 } 21 if(match(inp[x],inp[y])&&dp[x][y]==dp[x+1][y-1]){ 22 printf("%c",inp[x]); print(x+1,y-1); printf("%c",inp[y]); 23 return; 24 } 25 for(int k=x;k<y;k++){ 26 if(dp[x][y]==dp[x][k]+dp[k+1][y]){ 27 print(x,k); print(k+1,y); 28 return; 29 } 30 } 31 } 32 int main(){ 33 int kase; 34 int flag=0; 35 scanf("%d",&kase); 36 getchar(); 37 while(kase--){ 38 gets(inp); 39 gets(inp); 40 int len=strlen(inp); 41 if(len==0){ 42 if(flag++) printf(" "); 43 printf(" "); 44 } 45 else{ 46 for(int i=0;i<len;i++){ 47 dp[i+1][i]=0; 48 dp[i][i]=1; 49 if(i<len-1){ 50 if(match(inp[i],inp[i+1])) dp[i][i+1]=0; 51 else dp[i][i+1]=2; 52 } 53 } 54 for(int i=2;i<len;i++){ 55 for(int j=0;j+i<len;j++){ 56 dp[j][j+i]=len; 57 if(match(inp[j],inp[j+i])) dp[j][j+i]=min(dp[j][j+i],dp[j+1][j+i-1]); 58 for(int k=j;k<j+i;k++) 59 dp[j][j+i]=min(dp[j][j+i],dp[j][k]+dp[k+1][j+i]); 60 } 61 } 62 if(flag++) printf(" "); 63 print(0,len-1); 64 printf(" "); 65 } 66 } 67 return 0; 68 }