【题解】P2470 压缩
前言
正解:区间/线性 dp(本篇题解介绍线性做法)
人生第一道紫题!
也是今天考试看自闭了就没做的 T4,结果没想到是紫,虽然是一道水紫呢……
考试的 T5 是跳房子,蓝题 qwq。要不是前三题比较简单 + 骗分好骗(指靠直接输出字符串长度骗了十分)就真的自闭了。
不管怎么样反正挺难的。
题解
我们观察到一个字符串压缩的程度和 “M”,“R”的个数是有关的,尤其是开始一段压缩区间的“M”,非常的重要,因为它的位置决定了压缩串的长度,也直接决定了最后字符串的长度。非常好的条件,可以利用。
所以,定义 \(dp\) 数组状态为:
- \(dp_{i,j}\) 代表前 \(i\) 个字符,上一个“M”的位置在第 \(j\) 个字符前所能达到的最短长度。
\(dp\) 数组的初始值:
当只有一个字符时,“M”只有可能在第一个字符前,而且长度为 \(1\)。所以,\(dp_{1,1}=1\)。
于是我们可以分析出以下三种情况。
-
直接在前面 \(i-1\) 个字符的情况下,加上一个字符。
-
添加压缩的字符串。
-
新建一个压缩字符串部分的开头。
part1
如果只是直接加一个,这样 \(j\) 的值是不会变的,因为我们没有考虑“M”和“R”的情况。这样得到的答案就是 \(dp_{i-1,j}+1\)。
part2
我们知道,当且仅当某两个字符子串完全相同时,我们才能对这两个字符串进行一次压缩。
至于这个压缩的地方加在哪里呢?因为最近的“M”的位置已经确定,所以这个“M”(坐标为 \(j-1\) 和 \(j\))之前(并不包括第 \(j\) 个字符)的所有字符串情况已经确定,无法改动(因为 dp 不能有后效性)。
所以我们确定被复制了一遍的区域在 \([j,i]\),从中间切开这份字符串,看看两边是不是相等的,如果是就可以考虑这种情况。注意这段区域的的长度为奇数是是一定不可能的。
判断字符串是否相等的函数很简单,就是这样:
bool check(int l,int mid,int r){
if((r-l+1)&1) return 0; //如果长度是奇数直接不可能
for(int i=0;i<=mid-l;i++){
if(a[l+i]!=a[mid+1+i]) return 0; //出现不一样的字符
}
return 1; //全部都一样
}
然后考虑如何状态转移,很明显,这样的长度就是 \(j\) 之前的那些字符的最短长度 + 压缩后 \([j,i]\) 段的长度 + \(1\)(那个“R”字符)。转化为情况二的动态转移方程即为:
其中 \(\lfloor \dfrac{i+j}{2} \rfloor\) 表示的是被切成两段的那一部分的前面那个字符串的最后一个字符串的下标(好绕啊)。
part3
因为要新建一个“M”,意思就是再开一个可重复的字符串并将新开的“M”的下标作为 \(dp\) 数组的第二个值。因为我们枚举到 \(i\) 了,所以最方便的就是把这个“M”加在 \(i-1\) 和 \(i\) 之间,这样每一次循环都能考虑到一层,就全部考虑到了。
那转移方程是什么呢?我们继续观察,现在我们考虑的范围就已经缩小到了 \(1\) 到现在添加的“M”(坐标为 \(i-1\) 和 \(i\) 之间)中间的这一段。很明显 \(i\) 已经在这个“M”后面了,此时我们不需要考虑,所以只需考虑 \([1,i-1]\) 压缩后的最小长度。
那第二个下标呢?因为考虑前面的时候并没有考虑添加的“M”(这是两个情况),所以依然是枚举 \(j\)。
对了,这个值还要 \(+2\),一个是添加的“M”字符,另一个是下标为 \(i\) 的字符。(是的!这个也要算进去!因为此字符是在添加的“M”后面的,前面的式子并没有考虑到)。
所以,情况三的转移方程是:
将三个合并一下,不过记得,要保存前面情况的最优解。除了上述的特殊初始值外,因为求的是最小值,所以 \(dp\) 初始值设为 \(\text{INF}\)(极大值)。
Code
#include<cstdio>
#include<cstring>
#include<climits> // INT_MAX的头文件
#define min(a,b) (a)<(b)?(a):(b)
int n,dp[505][505];
char a[505];
bool check(int l,int mid,int r){
if((r-l+1)&1) return 0;
for(int i=0;i<=mid-l;i++){
if(a[l+i]!=a[mid+1+i]) return 0;
}
return 1;
}
int main(){
//freopen("compress.in","r",stdin);
//freopen("compress.out","w",stdout);
memset(dp,127,sizeof(dp));
dp[1][1]=1;
scanf("%s",a+1);
n=strlen(a+1);
for(int i=2;i<=n;i++){
for(int j=1;j<i;j++){
dp[i][j]=dp[i-1][j]+1;
if(check(j,(j+i-1)/2,i)) dp[i][j]=min(dp[i][j],dp[(i+j-1)/2][j]+1);
}
for(int j=1;j<i;j++) dp[i][i]=min(dp[i][i],dp[i-1][j]+2);
}
int ans=INT_MAX;
for(int i=1;i<=n;i++) ans=min(ans,dp[n][i]);
printf("%d",ans);
return 0;
}
写在最后
考试竟然有个 dalao (zdj 大佬!)写出来了啊……%%%。
虽然听着不难但是思路很难想呢 qwq。
最后请随手点个赞吧,毕竟赠人玫瑰手有余香嘛。