UVa 1630 Folding
题目分析
本题要求我们将一个由大写字母组成的字符串压缩成最短的形式,压缩规则是使用 数字(重复子串)
的格式表示重复部分。
压缩规则
- 单个字符保持原样
- 如果 SSS 和 QQQ 是压缩后的字符串,那么 SQSQSQ 也是压缩后的字符串
- 如果 SSS 是压缩后的字符串,那么 X(S)X(S)X(S) 也是压缩后的字符串,其中 XXX 是大于 111 的整数,表示 SSS 重复 XXX 次
问题示例
输入字符串 AAAAAAAAABABABCCD
可以压缩为 9(A)3(AB)CCD
。
问题本质
这是一个区间动态规划问题,我们需要找到字符串的最优折叠方式,使得压缩后的字符串长度最短。
解题思路
动态规划定义
设 dp[i][j]dp[i][j]dp[i][j] 表示子串 s[i..j]s[i..j]s[i..j] 的最短压缩字符串表示。
状态初始化
- 对于长度为 111 的子串,dp[i][i]=s[i]dp[i][i] = s[i]dp[i][i]=s[i](即单个字符本身)
状态转移
对于每个子串 s[i..j]s[i..j]s[i..j],我们考虑两种压缩方式:
-
分割压缩:将子串分成两部分 s[i..k]s[i..k]s[i..k] 和 s[k+1..j]s[k+1..j]s[k+1..j],分别压缩后再拼接:
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j])dp[i][j] = \min(dp[i][j], dp[i][k] + dp[k+1][j]) dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j])
其中 i≤k<ji \leq k < ji≤k<j -
重复模式压缩:如果整个子串可以由一个更短的子串重复多次构成,则使用 X(S)X(S)X(S) 的形式:
dp[i][j]=min(dp[i][j],to_string(times)+‘(’+dp[i][i+l−1]+‘)’)dp[i][j] = \min(dp[i][j], \text{to\_string}(times) + ‘(’ + dp[i][i+l-1] + ‘)’ ) dp[i][j]=min(dp[i][j],to_string(times)+‘(’+dp[i][i+l−1]+‘)’)
其中 lll 是重复单元的长度,times=lenltimes = \frac{len}{l}times=llen 是重复次数
重复模式检查
对于子串 s[i..j]s[i..j]s[i..j],长度为 len=j−i+1len = j-i+1len=j−i+1,我们检查所有可能的重复单元长度 lll(1≤l<len1 \leq l < len1≤l<len):
- 必须满足 len%l==0len \% l == 0len%l==0
- 必须满足 s[i..j]s[i..j]s[i..j] 是由 s[i..i+l−1]s[i..i+l-1]s[i..i+l−1] 重复 times=len/ltimes = len/ltimes=len/l 次构成
算法复杂度
- 时间复杂度:O(n3)O(n^3)O(n3),其中 n≤100n \leq 100n≤100,完全可行
- 空间复杂度:O(n2)O(n^2)O(n2)
代码实现
// Folding
// UVa ID: 1630
// Verdict: Accepted
// Submission Date: 2025-10-19
// UVa Run Time: 0.110s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;string fold(const string& s) {int n = s.size();vector<vector<string>> dp(n, vector<string>(n));// 初始化:长度为1的子串for (int i = 0; i < n; i++) dp[i][i] = s[i];// 枚举所有子串长度for (int len = 2; len <= n; len++) {for (int i = 0; i + len - 1 < n; i++) {int j = i + len - 1;// 初始化为原子串dp[i][j] = s.substr(i, len);// 1. 尝试分割点for (int k = i; k < j; k++) {string candidate = dp[i][k] + dp[k + 1][j];if (candidate.size() < dp[i][j].size()) dp[i][j] = candidate;}// 2. 尝试重复模式for (int l = 1; l < len; l++) {if (len % l == 0) {bool repeatOk = true;// 检查是否满足重复条件for (int pos = i; pos <= j; pos++) {if (s[pos] != s[i + (pos - i) % l]) {repeatOk = false;break;}}if (repeatOk) {int times = len / l;string candidate = to_string(times) + "(" + dp[i][i + l - 1] + ")";if (candidate.size() < dp[i][j].size()) dp[i][j] = candidate;}}}}}return dp[0][n - 1];
}int main() {string line;while (getline(cin, line)) {if (line.empty()) continue;cout << fold(line) << '\n';}return 0;
}
代码说明
- 数据结构:使用二维向量
dp
存储所有子串的最优压缩结果 - 初始化:单个字符的压缩结果就是字符本身
- 动态规划填充:按子串长度从小到大计算,确保子问题先被解决
- 两种压缩策略:
- 分割策略:枚举所有可能的分割点
- 重复模式:检查所有可能的重复单元长度
- 最优选择:总是选择长度最短的压缩表示
总结
本题通过区间动态规划的方法,系统地考虑了字符串的所有可能压缩方式,确保了找到最优解。关键点在于正确处理两种压缩策略:分割拼接和重复模式,其中重复模式的检查是算法的核心难点。