题目描述
难度分:2400
输入长度≤8000的字符串s,只包含小写英文字母。
你需要压缩s=s1×c1+s2×c2+…
例如abababcc=ab×3+c×2。
压缩后的长度定义为|s1|+|c1|+|s2|+|c2|+…
其中|c1|表示整数c1的十进制长度。例如ab×3+c×2的长度为2+1+1+1=5。
输出s压缩后的最短长度。
输入样例1
aaaaaaaaaa
输出样例1
3
输入样例2
abcab
输出样例2
6
输入样例3
cczabababab
输出样例3
7
算法
划分型DP
+KMP算法
首先可以发现一个比较显然的性质,对于任意一段子串[l,r],把它压缩成若干个最小周期是最划算的(数字长度的增加是很慢的,多举几个例子就能发现这一点)。
状态定义
dp[i]表示前缀[1,i]压缩之后的最短长度,在这个定义下答案就是dp[n]。
状态转移
枚举最后一段[j,i](j≤i),状态转移方程即为dp[i]=dp[j−1]+cost(j,i),其中cost(j,i)是子串[j,i]压缩之后的最短长度。因此我们只需要求出子串[j,i]的最短周期T就可以了,这时候能将[j,i]表示为cnt=i−j+1T个T连接而成,cost(j,i)=|T|+bits(cnt),其中bits(x)表示数字x的数位数,|T|表示字符串T的长度。枚举左端点l∈[1,n],对于固定的左端点l,利用KMP算法遍历右端点r∈[l,n]计算子串[l,r]的最小周期T,然后进行状态转移dp[r]=minl≤r(dp[l−1]+|T|+bits(r−l+1T))。
复杂度分析
时间复杂度
状态数量是O(n),单次转移的时间复杂度为O(n),所以整个算法的时间复杂度为O(n2)。
空间复杂度
除了输入的字符串s,DP
的状态数组dp空间复杂度为O(n);[1,8000]每个数的数位存在bits数组中,空间复杂度为O(n);KMP时需要用到的fail数组(或者next数组),空间复杂度为O(n)。因此,整个算法的额外空间复杂度为O(n)。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 8010, INF = 0x3f3f3f3f;
int n, dp[N], fail[N], bits[N];
char s[N];
int get_bits(int x) {
int ans = 0;
while(x > 0) {
ans++;
x /= 10;
}
return ans;
}
void init(int n) {
for(int i = 0; i <= n; i++) {
dp[i] = INF;
bits[i] = get_bits(i);
}
}
int main() {
scanf("%s", s + 1);
n = strlen(s + 1);
init(n);
dp[0] = 0;
for(int l = 1; l <= n; l++) {
fail[l] = 0;
for(int r = l + 1; r <= n; r++){
fail[r] = 0;
int k = fail[r - 1];
while(1) {
if(s[r] == s[l + k]){
fail[r] = k + 1;
break;
}
if(k == 0) break;
k = fail[l + k - 1];
}
}
for(int r = l; r <= n; r++) {
// 把子串[l,r]压缩
int len = r - l + 1;
int f = fail[r];
int cnt = 1;
if(len % (len - f) == 0) {
cnt = len / (len - f);
}
int T = len / cnt;
dp[r] = min(dp[r], dp[l - 1] + bits[cnt] + T);
}
}
printf("%d\n", dp[n]);
return 0;
}