括号匹配(POJ2955)题解
原题地址:http://poj.org/problem?id=2955
题目大意:给出一串括号,求其中的最大匹配数。
我这么一说题目大意估计很多人就蒙了,其实我看到最开始的时候也是很蒙的。这里就来解释一下题意。
这道题让求的是最大常规匹配数,什么是常规匹配呢?
() [] ()[] []() [()]都是常规序列。
翻译一下题目的英文:
我们给出了“常规括号”序列的以下归纳定义:
- 空序列是常规括号序列,
- 如果s是常规括号序列,则(s)和[ s ]是常规括号序列,并且
- 如果a和b是常规括号序列,则ab是常规括号序列。
- 没有其他序列是常规括号序列
样例输入
((()))
()()()
([]])
)[)(
([][][)
end
样例输出
6
6
4
0
6
现在开始说思路。
很明显,这是一道区间DP,因为大的常规括号一定是包含小的常规括号的,满足我们区间DP的要求。
区间DP常用枚举方式见
https://www.cnblogs.com/lizitong/p/10014809.html
首先是状态,非常简单,dp[i][j]表示从i到j的最大匹配数,我们要求的答案就是dp[1][n]
我们开始枚举,假如我们枚举到的[i][j]中,第i个和第j个是匹配的括号,即()或者[ ]
那么dp[i][j]=dp[i+1][j-1]+2;
没错吧,我们只需要在原基础上+2就好了。
如果[i] 和[j]不匹配呢?
那我们可以把这个区间分为两段,以k为分界点。
分成[i][k],[k+1][j]两段,然后把两段的值相加就好了。因为这两个括号不匹配,不代表其中的不匹配。人不能在一棵树上吊死。
需要注意的是,即使是[i]和[j]两个括号匹配,仍需枚举k来判断是否可以将这个区间分成两段,因为这样得到的仍然可能不是最优解。
例如:()()这个序列。
显然答案应该为4,但是最左面和最右面匹配,而中间 ")(" 区间的答案为0,导致最后的结果为2。
枚举k的时候需要注意枚举范围。
上代码。
提示:个人区间是从0存到的n-1。
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
using namespace std;
char a[105];
int dp[105][105];
int main()
{
while(scanf("%s",a)!=EOF)
{
if(a[0]=='e')
{
return 0;
}
int n = strlen(a);
memset(dp,0,sizeof(dp));
for(int len = 2;len<=n;len++)
{
for(int i = 0;i+len-1<=n-1;i++)
{
int j = i+len-1;
if((a[i]=='('&&a[j]==')')||(a[i]=='['&&a[j]==']'))
{
dp[i][j] = max(dp[i][j],dp[i+1][j-1]+2);
}
for(int k = i;k<=j;k++)
{
dp[i][j] = max(dp[i][j],dp[i][k]+dp[k+1][j]);
}
}
}
printf("%d\n",dp[0][n-1]);
}
return 0;
}