整理一下关于区间动态规划的题,《括号配对》和《密码脱落》
括号配对
括号的配对,即可以包含着配对(()),也可并列配对()().
F[i][j]状态表示
集合:所有将[i,j]变成合法括号的序列的集合。
属性:最小值
i和j为遍历时候区间的两端。
状态计算
先分为包含配对和并列配对两类。
1.包含配对f[i+1][j-1] (i,j即左右都被用上)
f[i][j-1]+1 ( j需要添加,即i被用上)
f[i+1][j]+1(i需要被添加,即j被用上)
这里的i被用上其实没有一个确定的状态表示,但f[i][j-1]包含了这个状态,并且求的是最小值,所以没有影响。
2.并列配对 f[i][k]+f[k+1][j]
即在i和j中找是否存在已经配对好的[i,k]和[k+1,j],[i,k]是配对好的没有一个状态能表示,但是可以用包含它的f[i][k]来表示。
即f[i][k]可能是(()()())这样的,而[i,k]是表示左边第一个()。
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
//集合:所有将[i,j]变成合法括号序列的方案的集合
//属性:最小值
//第一大类:相当于括号回文
//第二大类:并列的组
const int N = 110, INF = 100000000;
int n;
int f[N][N];
bool is_match(char l, char r)
{
if (l == '(' && r == ')') return true;
if (l == '[' && r == ']') return true;
return false;
}
int main()
{
string s;
cin >> s;
n = s.size();
for (int len = 1; len <= n; len ++ )
for (int i = 0; i + len - 1 < n; i ++ )
{
int j = i + len - 1;
//也可以设置成1
//因为i和j相同时,代表只有一个括号,那么就需要一个配对
f[i][j] = INF;
//左边
//左右匹配 则不需要配对
if (is_match(s[i], s[j]))
f[i][j] = f[i + 1][j - 1];
//左右不匹配
//有存在没配对的,所以有个加1
//这里看的是 不算在里面的那个端点
//如果有不算在里面,那就加一个,即配对一下
//结果找的是 要配对的 括号个数的 最小值
if (j >= 1)
f[i][j] = min(f[i][j], min(f[i][j - 1], f[i + 1][j]) + 1);
//右边第二大类
//有个k+1,所以k不能=j
for (int k = i; k < j; k ++ )
f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j]);
}
//左端点 到 右端点
cout << f[0][n - 1] << endl;
return 0;
}
密码脱落
#include <cstdio>
#include <string.h>
//加上一个字母 等价于 将其删去
//所求的即为 总长度 - 最大回文串
//这个 回文串可以不连续
//即问题变成了 如何寻找 最大回文子序列
//集合:l 到 r 之间的回文子序列的集合
//属性:长度的最大值
//写了一个l或者r肯定是包含之前状态的
//至于之前那个状态是啥不确定
//所以才写了这么多情况讨论
const int N = 1010;
char s[N];
int f[N][N];
int main()
{
scanf("%s", s);
int n = strlen(s);
//先循环区间长度1~n
//在循环左端点,最后把右端点算出来
//右端点:左端点+长度-1 记住!!!!!!!
//从0开始,右端点<n
//如果从1开始,右端点<=n
for (int len = 1; len <= n; len ++ )
for (int l = 0; l + len - 1 < n; l ++ )
{
int r = l + len - 1;
if (len == 1)
f[l][r] = 1;
else
{
//l和r不在里面
//即问题转变成了求 f[l + 1][r - 1] 的最大值
//再把l和r加上
if (s[l] == s[r])
f[l][r] = f[l + 1][r - 1] + 2;
//r没有找到配对
//那么右边的集合里必须有一个和 l相等
//状态里面没有说 哪个一定与l相等
// [l,r-1] 包含上面的集合,即可能有与l匹配的,也可能没有
//如果这种选择比f[l][r]大,即有与l匹配的
//取的是最大,所以可以这么比
//因为l是从小到大枚举
//所以当前状态的前面状态肯定是已经计算过的
if (f[l][r - 1] > f[l][r])
f[l][r] = f[l][r - 1];
if (f[l + 1][r] > f[l][r])
f[l][r] = f[l + 1][r];
//上面两种情况包含了f[l+1,r-1]
}
}
printf("%d\n", n - f[0][n - 1]);
return 0;
}