URAL 1635. Mnemonics and Palindromes(区间dp)
题目链接
题目大意:给定一个字符串,问分割成最少的子串,是每个子串均为回文串。
题目分析:可以用普通的区间dp做法,但是字符串的长度达到了4000,n^3会超时,要想办法降到n^2。想了很久,没有想出来,看了博客的做法。先预处理出来区间[i, j]是否为回文串。假如字符串的长度为10, 那么以6为长度的字符串的最少分割数就等于min(dp[1~5] && is[1~5]。好像说的不太清,看代码吧。
#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
const int maxn = 4e3 + 100;
const int inf = 0x3f3f3f3f;
int dp[maxn], ans[maxn];
bool is[maxn][maxn], vis[maxn];
char str[maxn];
//dp[i]表示从1~i的最小分割数
int main()
{
scanf("%s", str + 1);
int len = strlen(str + 1);
for(int i = 1; i <= len; i++) {
is[i][i] = true;
int l = i - 1, r = i + 1;
while(1 <= l && r <= len && str[l] == str[r]) {
is[l][r] = true;
l--; r++;
}
l = i; r = i + 1;
while(1 <= l && r <= len && str[l] == str[r]) {
is[l][r] = true;
l--; r++;
}
}
memset(dp, inf, sizeof(dp));
memset(ans, 0, sizeof(ans));
dp[0] = 0;
for(int i = 1; i <= len; i++) { //枚举长度,因为6这个长度的结果是可以用5推过来的。
for(int j = 1; j <= i; j++) { //枚举当前长度的断开点
if(is[j][i] && dp[i] > dp[j - 1] + 1) {
dp[i] = dp[j - 1] + 1;
ans[i] = j - 1; //记录断开点
}
}
}
printf("%d\n", dp[len]);
int pre = ans[len];
while(pre) {
vis[pre] = true;
pre = ans[pre];
}
for(int i = 1; i <= len; i++)
{
printf("%c", str[i]);
if(vis[i]) printf(" ");
}
printf("\n");
}
后来的时候,又思考了一下,为什么这个可以用一维来做,之前的取石子问题,括号匹配不能。我认为,是因为这个题dp[i]的状态可以由dp[i - 1]推过来,但是之前的问题都是不可以的。