题目链接
事实证明,一道题如果你第一次做错了,那么第二次做的时候很可能依旧会错.
当初写这道题就被细节折磨了很久,现在第二次做还是被折磨了.
思路分析:记忆化搜索嘛,就是写主框架+抠递归边界.依旧是自顶向下的方法写.
主框架:
最终状态是一整条字符串需要的最小字符数.可以这么去考虑问题,如果说字符串的头尾是匹配的.那么我们可以消掉一组符号,然后求取[i+1,j-1]范围所需要的字符数.不过要注意的是.[i+1,j-1]范围的字符串需要的个数不一定就比[i,j]这个原先范围的字符串需要的个数少(这也是我第一次做的时候没遇到但是第二次做的时候遇到的问题).给个最简单样例那就是:()()这个样例.如果认为[i+1,j-1]范围上就一定比较少的话,那么得出的答案应该是2.但其实不消掉头尾的字符组合能得出更小的答案.最好的划分应该是[1,2]+[3,4] = 0.一开始也是考虑少了,以为范围变小了答案肯定会小.事实却不是这样的…划分区间就没啥难度了.常规的[i,k][k+1,j].
边界:
在第一次做的时候是比较困扰我的地方,不过现在用记忆化搜索的时候就比较容易考虑边界了(这也是我为什么更喜欢记忆化搜索来写某些dp的原因).
我们考虑分割到最小的子问题是怎样的.其实就是单个的像’[’,’)'这样的字符.这个边界所需要的匹配数就是1.
因为我们用了i+1,j-1.这个范围是有可能导致i>j的.而基于第一个边界.可以知道如果出现i>j就一定是由()或者[]转移过来的.那么这个边界的匹配数应该是0,因为已经是一个匹配的字符了.
边界问题就处理完了.只要加几行代码到主框架里面就可以了.
两个步骤弄清楚.代码也就好写了.
附上代码
#define LL long long
#include <iostream>
#include <cstdio>
#include <string.h>
#include <algorithm>
#include <cmath>
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 110;
char a[N];
int dp[N][N];
bool judge(char a,char b){
if(a == '[' && b == ']') return true;
if(a == '(' && b == ')') return true;
return false;
}
int dfs(int i,int j){
if(dp[i][j] != INF) return dp[i][j];
if(i > j) return dp[i][j] = 0;
if(i == j) return dp[i][j] = 1;
if(judge(a[i],a[j])) dp[i][j] = dfs(i+1,j-1);
for(int k=i;k<j;++k) dp[i][j] = min(dp[i][j],dfs(i,k)+dfs(k+1,j));
return dp[i][j];
}
int main(){
scanf("%s",a+1);
int n = strlen(a+1);
memset(dp,0x3f,sizeof(dp));
cout << dfs(1,n) << endl;
return 0;
}