1 条题解
-
0
问题理解
JOI超市有 天的销售额数据 。我们需要选择一组天数 展示,满足:
- 必须包含最后一天:
- 相邻两天间隔不超过 :(对 )
目标是最大化印象分——选中的天数中,销售额是前缀最大值的天数数量。
关键观察
1. 问题结构
- 这是一个序列上的最长上升子序列(LIS)变种问题
- 增加了两个约束:
- 必须包含最后一天
- 相邻选中天数间隔不超过
- 印象分实际上就是选中序列的严格递增子序列长度
2. 约束条件的转化
- 条件2(相邻间隔 ≤ )可以理解为:对于每个位置 ,它的前驱只能来自 区间
- 条件1(必须包含 )意味着我们最终需要计算以 结尾的最优解
3. 动态规划思路
定义 表示以第 天结尾,且满足间隔约束的最大印象分。则有:
$$dp[i] = 1 + \max_{j \in [i-D, i-1], \ A_j < A_i} dp[j] $$其中 需要满足 (严格递增),且位置在 之前最多 天内。
算法设计
1. 朴素动态规划
直接按照上述递推式计算,时间复杂度为 ,无法通过 的数据。
2. 优化思路
我们需要快速查询:
- 在区间 中
- 所有满足 的位置
- 的最大 值
这是一个二维偏序查询问题,可以使用数据结构优化。
3. 离散化与排序
- 首先将销售额 离散化(因为 范围较大)
- 按 从小到大处理每个位置,相同 按位置从大到小处理(避免相同值之间的转移)
4. 数据结构选择
- 线段树:维护位置区间 的最大 值
- 并查集:辅助跳过已处理的位置,优化查询范围
5. 算法流程
- 将 按 升序、 降序排序
- 初始化线段树,所有位置 值为
- 按排序顺序处理每个位置 :
- 在 区间内查询最大 值
- 将位置 的 值更新为
- 最终答案为
6. 查询优化技巧
使用并查集维护已处理的位置:
- 对于每个位置 ,找到 中第一个已处理的位置
- 通过并查集快速跳过连续已处理的区间
- 将查询范围缩小到实际可能转移的位置
算法正确性
1. 排序处理的正确性
- 按 升序处理确保了只从值更小的位置转移
- 相同值按位置降序处理避免了相同值之间的非法转移
2. 区间查询的完备性
- 并查集确保了查询区间内所有可能的前驱位置都被考虑
- 线段树保证了在合法区间内找到最大 值
3. 约束条件的满足
- 查询区间 保证了相邻间隔约束
- 最终答案 保证了必须包含最后一天
复杂度分析
时间复杂度
- 排序:
- 每个位置处理:
- 并查集查找:近似
- 线段树查询/更新:
- 总复杂度:
空间复杂度
- 存储 和 值:
- 线段树:
- 并查集:
- 总复杂度:
代码实现要点
1. 数据结构定义
struct Element { int id, a; // 位置,销售额 } e[N]; bool cmp(Element x, Element y) { if (x.a == y.a) return x.id > y.id; return x.a < y.a; } int t[N << 2]; // 线段树 int fa[N]; // 并查集 int dp[N]; // DP数组2. 线段树操作
modify(): 单点更新 值query(): 区间查询最大 值queryr(): 查找区间内第一个非零位置
3. 并查集优化
int find(int x) { if (fa[x] == x) { int pos = queryr(1, 1, n, max(x - d, 1)); if (pos <= x - 1) fa[x] = find(pos); return fa[x]; } return fa[x] = find(fa[x]); }4. 主算法流程
- 读取输入并离散化
- 按规则排序
- 初始化数据结构
- 按排序顺序处理每个位置
- 输出 (即线段树根节点)
算法标签
- 动态规划
- 线段树/树状数组
- 并查集
- 最长上升子序列
总结
本题通过将原问题转化为带约束的LIS问题,并利用数据结构优化转移过程。关键点在于:
- 按值排序处理,将二维偏序问题降为一维区间查询
- 使用线段树维护区间最大 值
- 通过并查集优化查询区间,快速定位可转移的前驱位置
算法在 时间内解决了问题,可以处理 的大数据规模。
- 1
信息
- ID
- 5932
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者