【题解】洛谷 P2470 [SCOI2007] 压缩
P2470 [SCOI2007] 压缩 - 洛谷 (luogu.com.cn)(这真的是紫吗,感觉也就蓝)
看到题,我学的自动机和找规律推公式都按压不住。
看到数据范围泻火了。
题意不要理解错了:
R 重复从上一个 M(如果当前位置左边没有 M,则从串的开始算起)开始的压缩。
也就是像:ABCABCABC 这样的串会压缩成 ABCRABC,而 ABCRR 解压后会是 4 个 ABC。
更清晰的说,只有 2 的次方个数的回文串才能用很多个连续的 R 压缩。
其它不符合条件(比如不是 类型的偶数,和所有奇数)个数的回文串,
只能压缩成很多组不同的 ,中间用 M 阻隔,就像转为二进制数一样。
比如说我有 7 个 ABC,那么最优方案就是先将 4 个 ABC 压缩成 ABCRR,放 M 在后面。
剩下的 3 个 ABC 分成 2 个 和 1 个,最后变成 ABCRRABCRABC。
时间复杂度可以到 左右,首先排除贪心(时间复杂度太小且不太好想)。
然后想 dp,发现这种区间合并挺像基因序列那道题的,考虑区间 dp。
回看题目,发现 M 的设置位置是很重要的,M 会把字符串分成两部分。
这相当于在字符串的中间(放 M 的位置),将字符串前面的字符清零了(从 R 的规则来看)。
因为被 M 分成的两段区域可以自由使用 R,互不干扰,
考虑 枚举区间的开头结尾和 M 的位置。
而 R 的连续合并操作,前面说过了只能是 的连续合并,
其他需要 M 阻隔的情况都可以先单独连续合并,然后通过上面枚举 M 位置合并起来。
所以我们这里先考虑最初始不使用 M 的情况,也就是两段单个回文串合并。
要求两段串一模一样,这里可以用哈希减小常数,但之前都已经 了,直接枚举也没事。
举个例子 ABCABCABCABC,就会先把前后两段 ABCABC 合并,
压缩后都是 4(3 [ABC] + 1 [R])个。
然后再把前后两个 ABCR 合并,变成 5(4 [ABCR] + 1 [R])个。
前面已经说过 M 分开的两端可以单独使用 R,而 R 的连续合并操作要求两段都没使用过 M。
那么就可以直接将有无使用过 M 加入 dp 状态定义:
(提示:
都是假设
是整个字符串的开头,即
前面放了 M)
表示
到
整一段字符串压缩得到最短的长度,
且没使用过 M 分割(但有可能使用过 R 来连续合并)。
表示
到
整一段字符串压缩得到最短的长度,
且使用过 M 分割(也可能根本用不上 M)。
注释代码:
#include<bits/stdc++.h>
using namespace std;const int N = 55;
int dp[N][N][2];
/*
(提示:dp[i][j][0/1] 都是假设 i 是整个字符串的开头,即 i 前面放了 M)dp[i][j][0] 表示 i 到 j 整一段字符串压缩得到最短的长度,
且没使用过 M 分割(但有可能使用过 R 来连续合并)。
dp[i][j][1] 表示 i 到 j 整一段字符串压缩得到最短的长度,
且使用过 M 分割(也可能根本用不上 M)。
*/char s[N];bool judge(int l, int r) {int mid = (l + r) >> 1;for (int i = l; i <= mid; i ++) {int j = mid + i - l + 1;if (s[i] != s[j]) {return 0;}}return 1;
}int main () {ios::sync_with_stdio(false);cin.tie(0);cin >> (s + 1);int n = strlen(s + 1);memset(dp, 0x3f, sizeof(dp)); // 求最小值,初始化最大值 for (int L = 1; L <= n; L ++) { // 先枚举区间长度(区间 dp 常规操作) for (int i = 1; i + L - 1 <= n; i ++) { // 再枚举区间开端 int j = i + L - 1; // 区间结尾dp[i][j][0] = dp[i][j][1] = j - i + 1; // 最坏情况就是根本压缩不了for (int k = i; k < j; k ++) {dp[i][j][1] = min(dp[i][j][1], min(dp[i][k][1], dp[i][k][0]) +1 + min(dp[k + 1][j][1], dp[k + 1][j][0]));// 枚举 M 使用位置,分成的前后两部分可以单独使用 R// 这里不在意前后两部分有没有自己用过 M// 只是保证后面部分的答案合法(k + 1 的前面放了 M) } for (int k = i; k <= j; k ++) {dp[i][j][0] = min(dp[i][j][0], dp[i][k][0] + (j - (k + 1) + 1));// i 到 j 不使用 M,那只有前半部分可以用 R// 后半部分只能不压缩(不能合法使用 R) }if ( (j - i + 1) % 2 == 0 && judge(i, j)) {// 长度是偶数,且前后两段字符串完全相同int mid = (i + j) >> 1; dp[i][j][0] = min(dp[i][j][0], dp[i][mid][0] + 1);// 两段合并(压缩完是连续 R 且不用 M) } }}cout << min(dp[1][n][0], dp[1][n][1]) << "\n";return 0;
}
