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}\) 的值域。
在每个块内维护一颗动态开点线段树,以在原序列中的位置为下标,如果在减的过程中,值域不在这一块,则暴力删除修改。
如何进行修改。
-
若此时节点最大值小于等于 \(x\),则可直接退出。
-
若此时节点最小值大于 \(x\),同样可以直接打上标记然后退出。
-
若都没有,则继续递归不断处理。
查询则可以直接像普通线段树一样查询。
考虑修改块的复杂度,每一个数最多改变 \(\log\) 次块,故复杂度为 \(O(n \log w \log n)\),其中 \(w\) 为值域。
在考虑查询和修改的复杂度,由于就是普通的在线段树上的操作,所以复杂度同样为 \(O(n \log w \log n)\)。
这样的代码,你会获得 \(53pts\) 的好成绩。
你会发现你 \(\text{MLE}\) 掉了。
算一下空间,你就发现这道题居然也卡了空间。
考虑一个以时间换空间优化线段树空间的方法。
可以发现,线段树最下面几层的节点,占据了大量空间,考虑把底层换为分块来解决。
每次遍历到叶子节点的块时,直接暴力操作。
这样虽然时间效率变低了,但空间就被大量优化。
底层分块的实现可以尝试使用链表来维护,算一种常数小也比较好写的方法。
-
插入一个数时,直接接在此时的末尾的后面,复杂度 \(O(1)\)。
-
删除一个数,同样是链表的基础操作,复杂度 \(O(1)\)。
-
在做减法操作,下穿标记等操作时,只能遍历整个链表,复杂度 \(O(len)\),其中 \(len\) 为链表长度。
这个玩意总得复杂度可能不怎么好算,但感觉也不会太慢。
实测,底层块长开 \(100\),\(\log\) 分块开 \(14\) 进制,跑得比较快。