P7447 [Ynoi2007] rgxsxrs 题解

· · 题解

可能更好的阅读体验

特别劝退的一道题目。

调了整整两天,码长 4.39kb -> 6.89kb

这道毒瘤题不仅卡时间,还卡空间。

题意

给定一个长为 n 的序列 a,需要实现 m 次操作:

1 l r x:表示将区间 [l,r] 中所有 >x 的元素减去 x

2 l r:表示询问区间 [l,r] 的和,最小值,最大值。

思路

一看到题以为是类似第二分块的做法,后来发现了强制在线和巨大的值域范围。

首先看到要维护的值有和,最小值,最大值,可以想到可能可以用线段树维护。

但又由于这是 \text{Ynoi},所以还需要套一个分块。

考虑对于值域进行分块。

由于值域有 10^9 的大小,所以普通的根号分块显然不行,可以考虑 \log 分块。

具体来说,就是将值域分成 \log 个块,第 i 个块所存放的是 a^i-a^{i+1} 的值域。

在每个块内维护一颗动态开点线段树,以在原序列中的位置为下标,如果在减的过程中,值域不在这一块,则暴力删除修改。

如何进行修改。

查询则可以直接像普通线段树一样查询。

考虑修改块的复杂度,每一个数最多改变 \log 次块,故复杂度为 O(n \log w \log n),其中 w 为值域。

在考虑查询和修改的复杂度,由于就是普通的在线段树上的操作,所以复杂度同样为 O(n \log w \log n)

这样的代码,你会获得 53pts 的好成绩。

你会发现你 \text{MLE} 掉了。

算一下空间,你就发现这道题居然也卡了空间。

考虑一个以时间换空间优化线段树空间的方法。

可以发现,线段树最下面几层的节点,占据了大量空间,考虑把底层换为分块来解决。

每次遍历到叶子节点的块时,直接暴力操作。

这样虽然时间效率变低了,但空间就被大量优化。

底层分块的实现可以尝试使用链表来维护,算一种常数小也比较好写的方法。

  1. 插入一个数时,直接接在此时的末尾的后面,复杂度 O(1)

  2. 删除一个数,同样是链表的基础操作,复杂度 O(1)

  3. 在做减法操作,下穿标记等操作时,只能遍历整个链表,复杂度 O(len),其中 len 为链表长度。

这个玩意总得复杂度可能不怎么好算,但感觉也不会太慢。

实测,底层块长开 100\log 分块开 14 进制,跑得比较快。

Code