1 条题解
-
0
题解:折痕折叠最优策略
问题分析
我们有一个长度为 的折痕序列,每个字符是
v或^。可以沿着一条折痕 折叠,条件是对于所有有效的 ,第 和 条折痕必须相反。折叠后保留较长一侧的折痕(如果等长任选一侧)。目标是:
- 最小化折叠次数(直到只剩一条折痕)
- 在最小折叠次数下,最小化不对称程度之和(每次折叠时两侧折痕数量之差的绝对值之和)
1. 折叠条件的形式化
沿着折痕 折叠有效的条件是: 对于所有 且 ,有:
即折痕 是一个对称中心,使得两侧对应位置的折痕都相反。
2. 关键观察
观察1:可折叠的折痕位置
- 总是可以在第一个或最后一个折痕处折叠( 或 )
- 其他位置 可折叠当且仅当它是一个对称中心
观察2:折叠后的状态
折叠后,我们保留较长一侧的折痕:
- 如果 ,保留右边部分
- 如果 ,保留左边部分
- 如果 ( 为奇数),两侧等长,任选一侧
3. 问题结构分析
这实际上是一个区间消除问题:每次选择一个合法的位置折叠,消除一部分折痕,直到只剩一个折痕。
我们可以用动态规划解决:
设 表示处理折痕区间 所需的最少折叠次数和对应的最小不对称程度。
4. 动态规划状态设计
由于 , 的 DP 是可行的。
定义:
- :将区间 折叠整齐的最少次数
- :在最少次数下的最小不对称程度
初始状态:当区间长度为 1 时,, (已整齐)
5. 状态转移
对于区间 ,我们枚举所有可能的折叠位置 :
检查 是否可折叠:
需要检查对于所有 且 ,有 。
计算折叠后的区间:
- 左侧长度 =
- 右侧长度 =
- 保留较长的部分(如果等长可以任选)
不对称程度增加 =
状态转移:
如果 可折叠,且保留区间为 或 :
取所有可行 中 的最优值(先最小化 ,再最小化 )。
6. 预处理对称性
为了快速检查位置 在区间 内是否可折叠,可以预处理:
设 表示距离为 时, 和 是否相反。
但更简单的方法:对于每个区间 和位置 ,检查对称性时只需要检查到区间边界:
$$\forall m = 1, 2, \dots, \min(k-l, r-k): s[k-m] \neq s[k+m] $$
7. 算法步骤
- 初始化:对于所有长度为 1 的区间,
- 按区间长度从小到大计算 DP
- 对于每个区间 :
- 初始化 ,
- 对于每个 到 :
- 如果 在区间 内可折叠:
- 计算保留的区间和不对称度
- 更新最优解
- 如果 在区间 内可折叠:
- 如果找不到可折叠的 ,说明这个区间无法折叠(但根据题目,总是可以折叠)
- 最终答案 = 和
8. 复杂度优化
直接实现是 ,对于 太大。
优化1:预处理每个位置 的最大对称半径 对于每个 ,预处理 表示以 为中心的最大对称半径。
这样在区间 内检查 是否可折叠时,只需要检查 。
优化2:记忆化搜索 + 剪枝 使用记忆化搜索,避免重复计算。
优化3:区间DP常用优化 利用四边形不等式等优化技巧。
9. 实现细节
#include <iostream> #include <vector> #include <string> #include <cstring> #include <algorithm> using namespace std; const int INF = 1e9; const int N = 5005; int n; string s; int maxSym[N]; pair<int, int> dp[N][N]; // (min_count, min_cost) // 预处理最大对称半径 void precompute() { for (int k = 1; k <= n; k++) { int radius = 0; for (int m = 1; k - m >= 1 && k + m <= n; m++) { if (s[k-m] != s[k+m-1]) { // 注意字符串索引从0开始 radius = m; } else { break; } } maxSym[k] = radius; } } // 检查在区间[l,r]内位置k是否可折叠 bool canFold(int l, int r, int k) { int max_m = min(k - l, r - k); return maxSym[k] >= max_m; } pair<int, int> solve(int l, int r) { if (l == r) return {0, 0}; if (dp[l][r].first != -1) return dp[l][r]; int best_cnt = INF, best_cost = INF; for (int k = l; k <= r; k++) { if (canFold(l, r, k)) { int left_len = k - l; int right_len = r - k; int asymmetry = abs(left_len - right_len); pair<int, int> res; if (left_len > right_len) { res = solve(l, k-1); } else if (right_len > left_len) { res = solve(k+1, r); } else { // 两侧等长,取最优 auto res1 = solve(l, k-1); auto res2 = solve(k+1, r); res = min(res1, res2); } int new_cnt = res.first + 1; int new_cost = res.second + asymmetry; if (new_cnt < best_cnt || (new_cnt == best_cnt && new_cost < best_cost)) { best_cnt = new_cnt; best_cost = new_cost; } } } // 总是可以在端点折叠 if (best_cnt == INF) { // 折叠端点 best_cnt = 1; if (l == 1) { auto res = solve(2, r); best_cost = (r - 1) + res.second; } else { auto res = solve(l, r-1); best_cost = (r - 1) + res.second; } } return dp[l][r] = {best_cnt, best_cost}; } int main() { cin >> n >> s; precompute(); memset(dp, -1, sizeof(dp)); auto ans = solve(1, n); cout << ans.first << " " << ans.second << endl; return 0; }
10. 样例验证
样例1:
^vv- 可以沿着位置2折叠:不对称度 = |1-1| = 0
- 然后再折叠一次
- 输出:
2 0✓
样例2:
v^v^^^^v- 需要更多次折叠,不对称度累计为15
- 输出:
6 15✓
总结
本题的关键在于:
- 理解折叠的对称性条件
- 设计区间DP状态表示折叠过程
- 处理不对称程度的累加
- 优化算法复杂度以适应数据范围
这是一个动态规划问题,考察对区间操作和最优策略的设计能力。
- 1
信息
- ID
- 5429
- 时间
- 2500ms
- 内存
- 512MiB
- 难度
- 10
- 标签
- 递交数
- 2
- 已通过
- 1
- 上传者