NYOJ 15 括号匹配(二)(区间DP)
题目链接:http://acm.nyist.net/JudgeOnline/problem.php?pid=15
第一次打区间DP,看了很多博客,还是觉得没有太大帮助(脑子比较笨……),后来翻开LRJ的算法艺术与信息学竞赛,终于弄懂了。把心得及书上的区间DP思想记录下来。
这道题有几种情况: 我们称刚好括号全部匹配为规则的,如 S 串为 [][()],则称S是规则的
①若S为 [S'] 或 (S'),则我们只需要把 S' 串变成规则的就可以了。
②若S为[S' 或 (S' ,则我们只需要把 S‘ 串变成规则的,最后再加一个] 或)就可以了。
③若S为 S'] 或 S'),则我们只需要把 S’ 串变成规则的,最后再在前面加一个[或(就可以了。
④若找S[i……j]变成规则的最小数目,就是S[i……k] 和 S[k+1……j] 最小数目找出来相加(区间DP的分区间)
首先我们把上面的思想转换成代码:
#include<stdio.h> #include<string.h> #include<stdlib.h> #define check(i,j) ((s[i]=='['&&s[j]==']')||(s[i]=='('&&s[j]==')')) #define min(a,b) ((a)<(b)?(a):(b)) #define INF 1000000 char s[110]; int bracket(int i,int j) { int k; int ans=INF; if(i>j) return 0; else if(i==j) return 1; else { if(check(i,j)) ans=min(ans,bracket(i+1,j-1)); if(s[i]=='['||s[i]=='(') ans=min(ans,bracket(i+1,j)+1); if(s[j]==']'||s[j]==')') ans=min(ans,bracket(i,j-1)+1); for(k=i;k<j;k++) { ans=min(ans,bracket(i,k)+bracket(k+1,j)); } } return ans; } int main() { int T,len,i,j,k; scanf("%d",&T); while(T--) { scanf("%s",s); len=strlen(s); printf("%d\n",bracket(0,len-1)); } return 0; }
我们发现样例是可以过的,但是输入括号多点时,就要运行很长时间,这是因为在递归过程中我们计算了很多原来已经计算过的值,但是我们没有保存,运算量虽length增加呈指数增长。(类似树形的结构遍历) 这里我们就有两种解决方法:
一、记忆化搜索
首先我们想到的就是记下我们算过的值,避免重复计算。dp[i][j] 表示 从第i个到第j个要完全匹配需添加最少的括号数。
有两个初始条件:①DP[i][j]=0 (j<i) ②DP[i][i]=1; (要使一个括号匹配肯定要再添加一个)、
AC代码如下:
#include<stdio.h> #include<string.h> #include<stdlib.h> #define check(i,j) ((s[i]=='['&&s[j]==']')||(s[i]=='('&&s[j]==')')) #define min(a,b) ((a)<(b)?(a):(b)) #define INF 1000000 char s[110]; int dp[110][110]; int bracket(int i,int j) { int k; if(dp[i][j]!=INF) return dp[i][j]; if(i>j) return 0; else if(i==j) return 1; else { if(check(i,j)) dp[i][j]=min(dp[i][j],bracket(i+1,j-1)); if(s[i]=='['||s[i]=='(') dp[i][j]=min(dp[i][j],bracket(i+1,j)+1); if(s[j]==']'||s[j]==')') dp[i][j]=min(dp[i][j],bracket(i,j-1)+1); for(k=i;k<j;k++) { dp[i][j]=min(dp[i][j],bracket(i,k)+bracket(k+1,j)); } } return dp[i][j]; } int main() { int T,len,i,j,k; scanf("%d",&T); while(T--) { scanf("%s",s); len=strlen(s); for(i=0;i<len;i++) for(j=0;j<len;j++) dp[i][j]=INF; printf("%d\n",bracket(0,len-1)); } return 0; }
二、化递归为递推(自底向上递推)
【关键一点!!:】我们需要按照一定的顺序计算dp的值,由于计算dp[i][j]时我们需要计算dp[i][j-1],dp[i+1][j],dp[i+1][j-1]
所以我们按照j-i递增的顺序计算dp[i][j]。
AC代码如下:
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<string.h> 4 #define check(i,j) ((s[i]=='['&&s[j]==']')||(s[i]=='('&&s[j]==')')) 5 #define min(a,b) ((a)<(b)?(a):(b)) 6 #define INF 1000000 7 int dp[101][101]; 8 char s[101]; 9 int main() 10 { 11 int T,i,j,k,p,n; 12 scanf("%d",&T); 13 while(T--) 14 { 15 scanf("%s",s); 16 n=strlen(s); 17 for(i=1;i<n;i++) 18 dp[i][i-1]=0; 19 for(i=0;i<n;i++) 20 dp[i][i]=1; 21 for(p=1;p<n;p++) 22 for(i=0;i<=n-p;i++) 23 { 24 j=i+p; 25 dp[i][j]=INF; 26 if(check(i,j)) 27 dp[i][j]=min(dp[i][j],dp[i+1][j-1]); 28 if(s[i]=='('||s[i]=='[') 29 dp[i][j]=min(dp[i][j],dp[i+1][j]+1); 30 if(s[i]==')'||s[i]==']') 31 dp[i][j]=min(dp[i][j],dp[i][j-1]+1); 32 for(k=i;k<j;k++) 33 dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]); 34 } 35 printf("%d\n",dp[0][n-1]); 36 } 37 return 0; 38 }
总结:我们应该真正理解状态如何转移的,注意边界条件,以及动态规划在解决最优子结构问题上的应用。