DP练习:压缩
Cry_For_theMoon · · 题解
传送门:SCOI2007,压缩
有一个看似正确但实际上错误的做法:
- 当前后两部分相同的时候,压缩成一个
- 否则枚举断点 k,把两部分相加
我们注意到题目中的一句话:R重复从上一个M算起,那么就不能产生嵌套的情况。
举个栗子:一个位于中间的子串:dzkblrh ak ioi ak ioi dzkblrh ak ioi(空格只是为了方便看清),那么按照上面的策略压缩:
1.压缩成M dzkblrh ak ioi ak ioi R
2.压缩成M dzkblrh M ak ioi R R
然而又因为 R 是和最近的 M 配对的,因此第二个 R 是和第二个 M 配对的,因此展开后是dzkblrh ak ioi ak ioi ak ioi ak ioi,这显然是错误的。
错误的原因:我们每次压缩的时候都添加了一个 M,使得外层出现的 R 对应到了内层的 M 上,压缩串解压后自然就不是原来的字符串,换言之:内层有无 M 会决定外层能否压缩
那么我们就以"有无 M ",把
还需要搞明白一件事:这个"放 M "的过程,是在当前这个状态里放,还是在上一步(即外层)放。在这里选择第二种方案,原因有二:
1.因为默认 1 之前是存在 M 的,因此选择第一种方案需要在
2. 如果出现了压缩的情况,选择第一种方案是先放个 M ,再把左半边复制出来,再放一个 R,那问题是左半边怎么处理,你要是选择压缩,就回到了一开始的情况;要是不压缩,万一出现左半部分还可以拆分成两个相同的部分(比如说原串是ab ab ab ab),又丢失了最优解。我们发现这样做非常难处理,因此选择第二种。
因此我们假设推导
先来考虑左半边和右半边相等的情况,值得注意的是,只有
然后是枚举断点 k 的情况,我们列出这两个方程:
Q:为什么在方程 1 中,左边是
A;
此时我们再回过头来看。一开始
最后千万不要忘记输出的是
照着本文思路应该很容易打出代码,不过要注意检查左右两遍是否相等的部分比较容易出错。
代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=100;
char str[maxn];
int n,f[maxn][maxn][5];
bool check(int start,int end){
//check返回[start,end]能否被分割成完全相等的两部分
int len=(end-start+1)>>1;
if((end-start+1)%2==1)return false; //特别要注意的是,奇数长度的串要特判(否则你是过不去样例#1的)
for(int i=start,j=start+len;i<start+len;i++,j++){
if(str[i]!=str[j]){
return false;
}
}
return true;
}
int main(){
scanf("%s",str+1); //str+1同样是一个字符数组的地址,这样我这种 for(1..n) 党就非常舒服了
n=strlen(str+1);
for(int i=1;i<=n;i++){
f[i][i][1]=1; //此时就是字符一个本身
f[i][i][2]=2; //字符本身 加上一个多余的'M'
}
for(int len=2;len<=n;len++){
for(int i=1;i<=n;i++){
int j=i+len-1;
if(j>n)break;
f[i][j][1]=f[i][j][2]=1e9; //因为是求最小值
//如果能分割成左右两部分,用它更新f(i,j,1)
if(check(i,j)){
int mid=(i+j)>>1;
f[i][j][1]=min(f[i][j][1],f[i][mid][1]+1);
}
//枚举断点k
for(int k=i;k<j;k++){
f[i][j][1]=min(f[i][j][1],f[i][k][1]+j-k);
f[i][j][2]=min(f[i][j][2],min(f[i][k][1],f[i][k][2])+min(f[k+1][j][1],f[k+1][j][2])+1);
}
}
}
cout<<min(f[1][n][1],f[1][n][2]);
return 0;
}