题目大意:给你一个由小括号和中括号组成的序列,要使这个序列的括号匹配至少添加一些括号,要求输出一种添加字符数最小的匹配方式
一道相对比较简单的DP
考虑一个子串s[i...j-1],用dp[i][j]表示要使这个子串匹配至少需要添加多少个字符,如果这个子串是匹配的,要么它可以从中间一分为二,左右两边都是匹配的,要么这个子串的两端是匹配的
所以容易得到递推方程
dp[i][j] = 1 (i + 1 == j)
dp[i][j] = min(dp[i + 1][j - 1],dp[i][k] + dp[k][j]) (s[i]与s[j - 1]匹配, i < k < j)
dp[i][j] = min(dp[i][k] + dp[k][j]) (s[i]与s[j - 1]不匹配, i < k < j)
最后为了方便输出,需要一个数组f来记录对每一个子串的最佳添加字符方式,即使匹配两端还是在某一位置切割,一个整数就够了
状态数即子串个数n^2,状态转移O(n),故时间复杂度O(n^3),对n=100足够了
下面是源代码
#include <cstdio>
#include <cstring>
#define MAXN 105
char s[MAXN]; // 保存输入的字符串
int dp[MAXN][MAXN]; // dp[i, j]表示[i,j)直接的字符串要匹配所需添加的最小字符数
int f[MAXN][MAXN]; // 记录切割点
void printResult(int i, int j) // 输出结果的函数
{
if (i >= j)
{
return;
}
if (j == i + 1)
{
switch (s[i])
{
case '(':
case ')':
printf("()");
break;
case '[':
case ']':
printf("[]");
break;
}
return;
}
int k = f[i][j];
if (k > 0)
{
printResult(i, k);
printResult(k, j);
}
else
{
printf("%c", s[i]);
printResult(i + 1, j - 1);
printf("%c", s[j - 1]);
}
return;
}
int main()
{
// 据说可能有空行的情况出现...所以用fgets
fgets(s, MAXN, stdin);
int l = strlen(s);
int i, j;
// 外层循环对考察子串的长度进行枚举
for (i = 0; i < l; ++i)
{
dp[i][i + 1] = 1;
}
for (int t = 2; t <= l; ++t)
{
for (i = 0; i <= l - t; ++i)
{
j = i + t;
dp[i][j] = (s[i] == '(' && ')' == s[j - 1]) || (s[i] == '[' && ']' == s[j - 1]) ? dp[i + 1][j - 1] : MAXN;
// 枚举切割点位置
for (int k = i + 1; k < j; ++k)
{
if (dp[i][j] > dp[i][k] + dp[k][j])
{
dp[i][j] = dp[i][k] + dp[k][j];
f[i][j] = k;
}
}
}
}
//printf("%d\n", dp[0][l]);
printResult(0, l);
printf("\n");
return 0;
}