1 条题解
-
0
「春季测试 2023」密码锁 题解
题目大意
有 个拨圈,每个拨圈有 ()个格子,每个格子有一个数字。可以对每个拨圈进行轮换操作,目标是找到一种拨圈状态的组合,使得所有行的最大值与最小值之差的最大值(称为松散度 )最小。
解题思路
核心算法:二分答案 + 滑动窗口 + 线段树优化
该解法针对不同的 值采用不同的优化策略,核心思想是通过二分答案来寻找最小的可行松散度。
算法架构
1. 整体框架:二分答案
int l = 0, r = maxn - minn; while (l < r) { int mid = (l + r) >> 1; if (check(mid)) r = mid; else l = mid + 1; } cout << l << endl;2. 检查函数
check(d)对于给定的松散度 ,检查是否存在可行的拨圈状态组合:
- 枚举偏移量 (从 1 到 )
- 对每个 调用
solve(d, x)检查可行性
3. 核心函数
solve(d, x)处理固定 和 的情况:
步骤1:生成候选状态
for (int i = 1; i <= n; i++) { for (int j = 0; j < k; j++) { // 检查约束:位置0 ≤ minn+d 且 位置x ≥ maxn-d if (a[j][i] > minn + d || a[(j + x) % k][i] < maxn - d) continue; // 记录其他位置的值 } }步骤2:滑动窗口检查
- 对候选状态按 坐标排序
- 使用滑动窗口维护满足条件的区间
- 对于 需要额外的约束检查
针对不同 k 值的特殊处理
k = 1
if (k == 1) { cout << maxn - minn << endl; continue; }- 只有一个状态,直接计算松散度
k = 2
if (k == 2) return true;- 只要每个拨圈都有可行选择就成功
k = 3
- 使用滑动窗口,检查是否所有列都出现在窗口中
k = 4
- 使用线段树维护额外约束
- 需要检查两个额外位置的值是否满足条件
关键数据结构
线段树(用于 k=4)
struct segment { int l, r, max, flag; } tree[N * 4];- 支持区间加值和区间最大值查询
- 用于检查额外位置的约束条件
多重集合(用于 k=4)
multiset<int> st[N];- 维护每个拨圈的额外位置值
- 用于计算线段树的更新区间
算法正确性分析
为什么这样设计?
-
二分答案的可行性:松散度具有单调性 - 如果 可行,那么所有 也可行
-
偏移量枚举:通过枚举位置 ,将问题转化为二维约束问题
-
滑动窗口优化:利用排序后的单调性,避免暴力枚举
-
线段树辅助:对于 的复杂约束,使用数据结构高效检查
复杂度分析
- 二分答案:
- 检查函数:
- solve函数:
- :
- :
- :
- 总复杂度:在数据范围内可接受
代码亮点
- 模块化设计:针对不同 值采用不同策略
- 数据结构优化:合理使用线段树和多重集合
- 滑动窗口技巧:利用排序优化搜索过程
- 边界处理:细致的约束条件检查
实现细节
关键函数说明
work(f, x, d, v)处理拨圈 的额外位置值 的添加/移除:
- 更新多重集合
- 计算受影响的区间
- 更新线段树
cha(k, x, y, v)线段树区间加值操作,支持懒标记
约束条件处理
对于每个拨圈的状态选择,需要满足:
- 位置 0 的值 ≤ minn + d
- 位置 x 的值 ≥ maxn - d
- 其他位置的值在合理范围内(通过滑动窗口和线段树检查)
总结
这个解法通过巧妙的问题转化和分层优化,成功解决了密码锁问题:
- 二分答案框架:将优化问题转化为判定问题
- 偏移量枚举:将环形轮换转化为线性约束
- 数据结构优化:针对不同复杂度使用合适的数据结构
- 滑动窗口技巧:利用单调性提高效率
该算法能够高效处理 的所有情况,是一个典型的多层次优化解决方案。
- 1
信息
- ID
- 5610
- 时间
- 2000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 3
- 已通过
- 1
- 上传者