1 条题解
-
0
题解
问题重述
我们有一个长度为 的数组 (初始全0),以及一个长度为 的操作序列。
操作有两种:- 修改操作 :将区间 全部赋值为 。
- 求和操作 :输出当前 。
现在有 个查询,每个查询给出 ,要求:
- 将 重置为全0。
- 依次执行操作序列中第 个操作。
- 将所有求和操作的结果累加作为答案。
我们需要对每个查询输出这个累加和。
难点分析
直接模拟每个查询会达到 的复杂度,不可行。
必须离线处理,利用操作和查询的结构特性。关键观察
1. 求和操作的贡献分解
设某次求和操作 ()询问区间 ,它的答案只取决于在它之前的最后一次覆盖每个位置的修改操作。
对于位置 ,设最后一次覆盖 的修改操作编号为 (如果不存在则 ),则该位置对 的贡献是 。
因此:
$$\text{answer}(op_i) = \sum_{p=l_i}^{r_i} v_{last(p)} $$
2. 查询的区间性
查询 要求我们只考虑操作 ,这意味着:
- 只有操作 中的修改会影响 。
- 只有操作 中的求和操作才计入答案。
因此, 是在 内、在 之前的最后一次覆盖 的修改。
3. 离线扫描线思路
将查询按 排序(或按 排序),在扫描过程中维护一个“当前操作前缀”的信息。
核心算法框架
第一步:转化贡献形式
考虑每个修改操作 (类型1)对后面求和操作 (类型2,)的贡献。
如果修改 覆盖区间 ,赋值为 ,那么对于 , 对 有贡献当且仅当:
- 与 相交(即修改覆盖的位置在求和区间内)。
- 在 和 之间没有其他修改覆盖相同位置(即 是这些位置在 之前的最后一次修改)。
设 表示在 之后、最早覆盖 中任意位置的修改操作编号(若不存在则 )。
$$j < i < next(j) \quad \text{且} \quad [l_j, r_j] \cap [l_i, r_i] \neq \varnothing $$
那么 对 有贡献的条件是:此时, 对 的贡献是:
第二步:查询转为二维贡献
查询 的答案是所有满足 的 对( 修改, 求和)的贡献之和。
即:
$$\text{ans}(L,R) = \sum_{j=L}^{R} \sum_{i=j+1}^{R} [op_j\text{是修改}] \cdot [op_i\text{是求和}] \cdot v_j \cdot |[l_j,r_j] \cap [l_i,r_i]| \cdot [i < next(j)] $$其中 是指示函数( 真时为1,否则为0)。
第三步:离线处理技巧
将查询按 升序排序。我们扫描 ,维护一个数据结构,使得当扫描到 时,能快速回答所有 的查询(对每个 求 的答案)。
对于每个 ,我们需要计算所有 的修改操作对以 结尾的查询的贡献。
设当前扫描到 ( 可能是求和或修改)。
情况1: 是求和操作
对每个修改操作 (),如果 且 ,则 对以 为结尾、且 的查询有贡献 。
我们需要快速计算:
$$\sum_{j=1}^{R-1} [op_j\text{是修改}] \cdot [j \ge L] \cdot [R < next(j)] \cdot v_j \cdot |[l_j,r_j] \cap [l_R,r_R]| $$对每个 的查询,这需要区间 中修改操作的贡献和。
情况2: 是修改操作
当 是修改时,它会“阻断”前面一些修改的 ,从而影响后面的求和操作。
具体来说,对每个 ,如果 ,那么 应更新为 (如果原来 )。这意味着 对 的求和操作不再有贡献(因为 )。
第四步:数据结构设计
我们需要维护一个集合 ,包含所有“活跃”的修改操作 ,即满足 的修改。
对每个活跃修改 ,我们需要知道:
- 它的区间 和值 。
- 能快速计算对当前求和操作 的交集长度贡献。
维护方法:
- 用线段树维护每个位置 上活跃的修改操作信息。
- 对位置 ,如果有活跃修改覆盖它,我们需知道是哪个修改(因为同一位置只能被一个活跃修改覆盖,这是由 定义保证的)。
- 实际上,对每个位置 ,覆盖它的最近修改操作是唯一的,我们可以在线段树上维护每个区间的“覆盖修改编号”和 值(如果整个区间被同一个修改覆盖,则可以直接计算贡献)。
第五步:扫描过程
- 将所有查询按 分组。
- 扫描 :
- 如果 是修改 :
- 找出区间 中所有被其他活跃修改覆盖的位置,将它们从活跃集合中移除(因为 变为 )。
- 将 标记为被修改 覆盖,值 ,加入活跃集合。
- 如果 是求和 :
- 查询线段树中区间 的和(每个位置的值是覆盖它的活跃修改的 值)。
- 这个和就是 的答案。
- 对每个以 为 的查询 ,我们需要 ,其中 是求和操作。
- 因此我们还需要维护一个前缀和数组,记录到当前位置 为止所有求和操作的答案前缀和,这样对查询 ,答案就是 (只考虑 中的求和操作)。
- 如果 是修改 :
- 但注意:查询 要求只执行 的操作,而上述扫描是从1开始的。因此我们需要能“扣除” 操作的影响。
第六步:处理任意 查询
经典技巧:将查询 拆分为 的答案减去 的答案,再减去 操作对 中求和的“遗留影响”。
更精确地,设 为查询的答案。
定义 为执行操作 得到的求和操作答案总和。
那么 ,因为 中的修改会影响 中的求和。
我们需要计算 中的修改对 中求和的贡献,这部分在 中但不在 中。
设 表示操作 对操作 中求和的贡献。
则:问题转化为计算 。
第七步:计算
是 中的修改对 中求和的贡献。
这要求修改 满足:- 是修改操作
- 且在 内(即 )
- 对 且 是求和操作,
我们可以用扫描线处理:
- 对每个修改 ,它在 内有效的区间是 。
- 对每个求和操作 ,它与修改 的交集贡献为 。
这可以看作二维数点问题:修改 在时间轴 有效,对空间区间 有贡献 ;求和操作 在时间 查询空间区间 的有效贡献和。
用线段树维护时间轴,每个时间点对应一个空间线段树(或树状数组),支持区间加、区间查询。但空间太大,需要进一步优化。
第八步:最终优化方案
注意到 都可达 ,需要 或更优的算法。
标准做法:
- 用线段树维护数组 ,支持区间赋值(修改操作)和区间求和(求和操作)。
- 对每个查询 ,我们无法在线处理,但可以离线分治(如时间分治或整体二分)。
- 时间分治(CDQ分治):
- 将操作序列按时间分治。
- 考虑跨过分治中点 的查询 ()。
- 对于 ,操作分为 和 。
- 左半的修改会影响右半的求和。
- 在分治过程中,用扫描线处理左半修改对右半求和的贡献。
- 在分治的每一层:
- 将左半的所有修改操作提取出来。
- 将右半的所有求和操作提取出来。
- 计算左半修改对右半求和的贡献(这是一个二维贡献问题,可用一维数据结构解决)。
这样总复杂度 ,可以接受。
算法步骤总结
- 读入 和操作序列。
- 将查询离线,准备进行时间CDQ分治。
- 分治函数
solve(l, r)处理操作时间在 的查询:- 若 ,直接处理单点查询。
- 令 。
- 递归处理 和 的查询。
- 处理跨中点的查询 (): a. 提取 中的所有修改操作。 b. 提取 中的所有求和操作。 c. 对修改操作,计算它对后面求和操作的贡献(需考虑 限制)。 d. 用扫描线+线段树计算贡献和。
- 汇总答案,输出。
复杂度分析
- CDQ分治共 层。
- 每层处理所有修改和求和操作,总操作数 。
- 每层用线段树维护区间贡献, 单次。
- 总复杂度 ,在 规模下可行。
关键难点
- 的处理:需要快速找到覆盖区间相交的下一个修改。
- 贡献的相交长度计算:需要高效计算 。
- 离线分治的边界处理:确保不重不漏。
- 空间限制:需用高效数据结构,避免 空间。
总结
本题是数据结构综合题,结合了区间赋值、区间求和、操作序列历史查询等多种要素。
通过离线分治将“修改对求和的影响”转化为二维数点问题,并用扫描线和线段树解决。
核心思想是将时间维度与空间维度分离,利用CDQ分治处理时间区间,用线段树处理空间区间,从而在可接受复杂度内完成所有查询。
- 1
信息
- ID
- 5828
- 时间
- 8000ms
- 内存
- 512MiB
- 难度
- 10
- 标签
- 递交数
- 2
- 已通过
- 1
- 上传者