1068: [SCOI2007]压缩
Time Limit: 1 Sec Memory Limit: 128 MBSubmit: 1574 Solved: 1004
[Submit][Status][Discuss]
Description
给一个由小写字母组成的字符串,我们可以用一种简单的方法来压缩其中的重复信息。压缩后的字符串除了小
写字母外还可以(但不必)包含大写字母R与M,其中M标记重复串的开始,R重复从上一个M(如果当前位置左边没
有M,则从串的开始算起)开始的解压结果(称为缓冲串)。 bcdcdcdcd可以压缩为bMcdRR,下面是解压缩的过程
另一个例子是abcabcdabcabcdxyxyz可以被压缩为abcRdRMxyRz。
Input
输入仅一行,包含待压缩字符串,仅包含小写字母,长度为n。
Output
输出仅一行,即压缩后字符串的最短长度。
Sample Input
Sample Output
HINT
在第一个例子中,解为aaaRa,在第二个例子中,解为bMcdRRxMcdRR。
【限制】
100%的数据满足:1<=n<=50 100%的数据满足:1<=n<=50
分析:挺巧妙的一道题.
首先可以一眼看出来这是一个区间dp,但是直接想状态以及状态转移方程很难.一般的区间dp状态都表示为f[l][r]表示将区间[l,r]处理后的最小长度,但在这题里似乎还缺点什么.
考虑一下搜索.首先需要知道,在串的左侧默认有一个“M”,这是不计入串长的.每次搜区间[l,r]能被压缩成多短.接下来你有几种操作:1.找个位置放R. 2.找个位置放M.
对于第一个操作,如果R放在串的中间正好,那么这个串的长度就缩成了一半,可以递归处理下去. 如果R放在偏右位置,没有意义.如果R放在偏左位置,那么可以将串一分为二,左边放了R的部分继续递归,右边就用原串,结束递归. question:为什么在这里不讨论右边放R呢?如果右边放R,那么前面必有一个M,这就属于第二个操作了.
对于第二个操作,找了一个位置放M后,可以发现左右两半就是一模一样的子问题了,递归下去.
可以发现,第二个操作的M对于第一个操作的R是一个制约左右,只有左边放了M才能放R.
到这里,记忆化搜索一下就可以了.当然,也可以用递推.令f[i][j][0/1]表示区间[l,r]中有没有放M压缩后最短的串. 具体的递推方程看我的代码吧:
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int inf = 0x7ffffff; char s[60]; int len,f[60][60][2]; bool check(int x,int y) { if ((y - x + 1) & 1) return false; int mid = (x + y) >> 1; for (int i = 1; i <= mid - x + 1; i++) { if (s[i + x - 1] != s[mid + i]) return false; } return true; } int main() { scanf("%s",s + 1); len = strlen(s + 1); for (int l = 1; l <= len; l++) { for (int i = 1; i + l - 1 <= len; i++) { int j = i + l - 1; f[i][j][0] = f[i][j][1] = (j - i + 1); for (int k = i; k < j; k++) f[i][j][1] = min(f[i][j][1],min(f[i][k][0],f[i][k][1]) + 1 + min(f[k + 1][j][0],f[k + 1][j][1])); for (int k = i; k < j; k++) f[i][j][0] = min(f[i][j][0],f[i][k][0] + j - k); if (check(i,j)) f[i][j][0] = min(f[i][j][0],f[i][(i + j) >> 1][0] + 1); } } printf("%d ",min(f[1][len][0],f[1][len][1])); return 0; }