洛谷 P2470 [SCOI2007]压缩
给一个由小写字母组成的字符串,我们可以用一种简单的方法来压缩其中的重复信息。压缩后的字符串除了小写字母外还可以(但不必)包含大写字母R与M,其中M标记重复串的开始,R重复从上一个M(如果当前位置左边没有M,则从串的开始算起)开始的解压结果(称为缓冲串)。
求一个仅含小写字母的字符串压缩后的最短长度
一道区间dp题,但是可能不太好想?
首先我们如果设\(f_{l,r}\)表示区间l~r的最短长度,发现M的位置是会影响我们统计的
所以我们不妨设\(f_{l,r,1/0}\)表示区间l~r有或没有M的最短长度,并且默认l-1的位置是有M的
这样就容易转移多了
\[\begin{cases}
f_{l,r,0}=f_{l,mid,0}+1\ \ \ (2|(r-l+1)\& s_{l\dots mid}=s_{mid+1\dots r}) \\
f_{l,r,0}=min_{k=l}^{r-1}(f_{l,k,0}+r-k) \\
f_{l,r,1}=min_{k=l}^{r-1}(min(f_{l,k,0},f_{l,k,0})+1+min(f_{k+1,r,0},f_{k+1,r,1}))
\end{cases}\]
第一种情况是左半边等于右半边,那么就可以被压缩,因为l-1有M,所以添个R就可以了
第二种情况是l~k这段被压缩过,因为这段区间没有M,所以后面不能被压缩,只能加上原长度
第三种情况因为这个区间插入了个M,所以两边有没有M无所谓了
Code
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
const int N = 50;
using namespace std;
int n,f[N + 5][N + 5][2];
char ch[N + 5];
int check(int l,int r)
{
int mid = l + r >> 1;
for (int i = l;i <= mid;i++)
if (ch[i] != ch[mid + i - l + 1])
return 0;
return 1;
}
int main()
{
scanf("%s",ch + 1);
n = strlen(ch + 1);
for (int i = 1;i <= n;i++)
for (int j = 1;j + i - 1 <= n;j++)
{
f[j][j + i - 1][0] = f[j][j + i - 1][1] = i;
int mid = (j + j + i - 1) >> 1;
if (i % 2 == 0 && check(j,j + i - 1))
f[j][j + i - 1][0] = min(f[j][j + i - 1][0],f[j][mid][0] + 1);
for (int k = j;k < j + i - 1;k++)
{
f[j][j + i - 1][0] = min(f[j][j + i - 1][0],f[j][k][0] + j + i - 1 - k);
f[j][j + i - 1][1] = min(f[j][j + i - 1][1],min(f[j][k][1],f[j][k][0]) + min(f[k + 1][j + i - 1][0],f[k + 1][j + i - 1][1]) + 1);
}
}
cout<<min(f[1][n][1],f[1][n][0])<<endl;
return 0;
}