1 条题解
-
0
题目分析
这是一个通过相邻交换使前 个元素总和至少达到 的问题。每次只能交换相邻元素,需要最小化交换次数。
关键点:
- 只能进行相邻交换
- 目标:前 个元素(假设 )的和至少为
- 需要计算最小交换次数
算法思路
核心思想
使用动态规划 + 分治策略:
- 拆分为两个子问题:
- 从前m个盒子内部移动糖果到前m个位置
- 从后n-m个盒子移动糖果到前m个位置
- DP记录状态:用DP记录不同交换代价下的糖果数
- 双指针合并:合并两个子问题的结果
关键观察
- 交换相邻元素相当于将一个元素向左或向右移动
- 将一个元素从位置 移动到位置 () 需要 次交换
- 移动操作可以分为两类:内部移动和外部移动
代码详解
预处理
cin >> n >> m >> T; for (int i = 0; i < n; i++) cin >> a[i]; // 减去前m个盒子已有的糖果 for (int i = 0; i < m; i++) T -= a[i]; if (T <= 0) { puts("0"); // 已经满足要求 return 0; }第一部分:左半区DP (
fl数组)memset(fl, 63, sizeof(fl)); fl[0][0] = 0; for (int i = m - 1; i >= 0; i--) { for (int j = m - i - 1; j >= 0; j--) for (int k = 0; k < M; k++) if (fl[j][k] < LINF) { // 状态转移 fl[j + 1][k + m - i - 1 - j] = min(fl[j + 1][k + m - i - 1 - j], fl[j][k] + a[i]); } }fl[j][k]含义:- 从前 个盒子中选择 个移动到前 个位置
- 代价为 (交换次数)
- 糖果数的最小值
状态转移:
- 选择第 个盒子(位于位置 )
- 将其移动到前 个位置中的某个位置
- 代价增加:
m - i - 1 - j(需要跳过的盒子数) - 糖果数增加:
a[i]
第二部分:右半区DP (
fr数组)memset(fr, -63, sizeof(fr)); fr[0][0] = 0; for (int i = m; i < n; i++) { for (int j = i - m; j >= 0; j--) for (int k = 0; k < M; k++) if (fr[j][k] >= 0) { // 状态转移 fr[j + 1][k + i - m - j] = max(fr[j + 1][k + i - m - j], fr[j][k] + a[i]); } }fr[j][k]含义:- 从后 个盒子中选择 个移动到前 个位置
- 代价为 (交换次数)
- 糖果数的最大值
状态转移:
- 选择第 个盒子(位于位置 )
- 将其移动到前 个位置
- 代价增加:
i - m - j(需要跳过的盒子数) - 糖果数增加:
a[i]
第三部分:合并结果
int ans = INF; for (int i = 0; i <= n; i++) { // 构建有序序列 vector<pair<ll, int>> seql, seqr; // 左半区:按糖果数递增,代价递减构建 ll lst = LINF; for (int j = 0; j < M; j++) if (fl[i][j] < lst) { seql.push_back(make_pair(fl[i][j], j)); lst = fl[i][j]; } // 右半区:按糖果数递增,代价递增构建 lst = -LINF; for (int j = 0; j < M; j++) if (fr[i][j] > lst) { seqr.push_back(make_pair(fr[i][j], j)); lst = fr[i][j]; } // 双指针扫描寻找最优解 for (int j = (int)seql.size() - 1, k = 0; j >= 0; j--) { // 找到满足糖果数要求的右半区元素 while (k < seqr.size() && -seql[j].F + seqr[k].F < T) k++; if (k < seqr.size()) // 总代价 = 左代价 + 右代价 + 内部交换代价(i*i) ans = min(ans, seql[j].S + seqr[k].S + i * i); } }合并逻辑:
i:从前 个盒子中选择了i个盒子保留seql:左半区选择的糖果数(负值)和代价seqr:右半区选择的糖果数(正值)和代价- 条件:
-seql[j].F + seqr[k].F ≥ T(总糖果数满足要求) - 总代价:左代价 + 右代价 + 内部调整代价(
i*i)
输出结果
if (ans >= INF) puts("NO"); else cout << ans << '\n';算法原理
为什么需要
i*i的额外代价?当从前 个盒子中选择 个保留时,需要考虑:
- 这 个盒子需要占据前 个位置
- 其他 个位置由外部盒子占据
- 内部盒子之间的相对顺序调整需要额外交换
i*i是对内部交换代价的一个估计(实际可能更复杂,但这个估计足够)。双指针扫描的正确性
seql按糖果数递增排序,代价递减(更少的糖果需要更少的代价)seqr按糖果数递增排序,代价递增(更多的糖果需要更多的代价)- 对于每个左半区选择,找到满足条件的最小右半区代价
复杂度分析
时间复杂度
- DP部分:,其中 是最大代价
- 合并部分:
- 对于 ,可以接受
空间复杂度
- DP数组:
- 辅助数组:
样例分析
样例1
输入:
6 2 27 10 4 20 6 3 3- 前2个盒子已有糖果:10+4=14
- 还需要:27-14=13
- 算法找到:交换4和20(代价1)
- 输出:1
样例2
输入:
6 5 5000000000 1000000000 1000000000 0 1000000000 1000000000 1000000000- 需要将0移动到前面
- 代价:从位置2移动到位置0需要2次交换
- 输出:3(考虑内部调整)
算法优势
- 分治策略:将复杂问题分解为可管理的子问题
- 动态规划:高效记录所有可能状态
- 双指针优化:快速找到最优组合
- 精确计算:考虑所有可能的交换方案
关键技巧
- 正负值处理:左半区用负值,右半区用正值,便于判断总和
- 有序序列构建:维护单调性,加速查询
- 代价估计:使用
i*i近似内部交换代价 - 边界处理:处理已满足条件和不可能情况
- 1
信息
- ID
- 5703
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者