历史和线段树
历史和线段树就是能在 \(O(n\log n)\) 中查询过去 \(q\) 个版本某个区间的和的总和。
形式化的说,有一个数组 \(a\) 和一个辅助数组 \(b\),每一次(广义)更新操作都会执行 \(a:[x,y] \rightarrow b:[x,y]\),查询 \(k\) 个版本后 \(b:[x,y]\) 的值(即 \(\sum\limits_{k}\sum\limits_{i = x}^y a_i\) 的值)。
矩阵法求解历史和
如何在 \(O(n\log n)\) 的时间内解决上述问题呢?
我们考虑使用线段树和矩阵。
LOJ193 【模板】线段树历史和
这是一道模板题。
您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:
- 区间加一个数;
- 查询区间的历史和;
历史和定义为数列 \(h_i\) 的区间和:初始 \(h_i=a_i\),在每次操作(修改或查询,具体可参考样例解释)完成后,对所有 \(h_i \leftarrow h_i+a_i\)。
以 LOJ193 为例,我们要求最朴素的历史和。
我们可以用线段树维护矩阵,其中矩阵为:
其中 \(his\) 为历史和,\(sum\) 为区间和,\(len\) 为区间长度。
其实就是用矩阵打包线段树上要维护的所有变量。
对于叶子节点,\(len = 1\) , \(sum = a_i\), \(his = 0\);对于非叶子节点,\(tag = \begin{bmatrix} 1 & 0 & 0\\ 0 & 1 & 0\\ 0 & 0 & 1\\ \end{bmatrix}\)。
然后是对线段树进行区间矩阵乘操作:
节点的合并(例节点 \(a + b \rightarrow c\)):
区间加 \(d\) 操作为:
区间历史和更新操作为:
我们将矩阵按线段树的方式下放到指定区间即可。
我们每次进行区间加 \(d\) 时,对全局进行历史和更新操作。
最后查询区间矩阵的历史和,只需按线段树的方式求区间矩阵的 \(\sum his\) 即可。
这样你就成功完成了此题!
实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | |
通过记录:
进一步优化
我们的矩阵乘法要维护两个 \(3 \times 3\) 矩阵相乘的结果,这带来的结果是常数来到了惊人的 \(27\),然而这是无法接受的!
这时聪明的奶龙就发现了,矩阵的好多地方是不变的!
我们可以用下面的代码来探究到底哪些矩阵元素永远不会变:
探究随机矩阵乘所固定的元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | |
我们会惊讶的发现,实际上矩阵中只有四个位置是在变化的:
那么,我们可以通过手摸矩阵来达到 \(3 \sim 4\) 的复杂度常数!
实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 | |
通过记录:
可以看到区别还是很大的!
推理法求历史和
待后人补充!
值得注意的事情
矩阵所维护的元素所执行的操作无非加减乘除,这是矩阵的作为线性代数的性质导致的。
也就是说,历史和线段树只能用来维护具有线性关系的元素!
所有对于一类历史和线段树问题,思路都是尝试转换成一系列线性关系的操作。
习题
codeforces 1824D: LuoTianyi and the Function