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;
}
View Code


我们发现样例是可以过的,但是输入括号多点时,就要运行很长时间,这是因为在递归过程中我们计算了很多原来已经计算过的值,但是我们没有保存,运算量虽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 }
View Code

 

 

  总结:我们应该真正理解状态如何转移的,注意边界条件,以及动态规划在解决最优子结构问题上的应用。

 

posted @ 2014-04-21 21:55  hjf007  阅读(534)  评论(0编辑  收藏  举报