线段树
线段树是一种 \(O(n)\) 建树,\(O(\log n)\) 查询区间半群信息(满足结合律),\(O(\log n)\) 单点赋值的数据结构。当和另一个运算满足(广义)分配律时,可以通过打 tag
的方式实现 \(O(\log n)\) 区间修改。如果修改操作和查询操作使用的运算相同,分配律退化为交换律。
维护差分数组
P4243 [JSOI2009] 等差数列
给定一个数列,实现两种操作:
- 区间加一个等差数列;
- 查询区间 \([l,r]\) 最少被拆成多少个等差数列;
代码
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 |
|
P5278 算术天才⑨与等差数列
你需要维护一个长度为 \(n\) 的序列,支持两种操作:
- 单点修改;
- 给定区间 \([l,r]\) 和常数 \(k\),询问区间内的数字能否重排成公差为 \(k\) 的等差数列;
\(n,m\le 3\times 10^5,\ 0\le a_i,k\le 10^9\),强制在线。
特判区间长度等于 \(1\) 和 \(k=0\) 的情况。发现区间合法当且仅当差分数组都是 \(k\) 的倍数,并且区间中不包含重复数字。第一个条件可以使用线段树维护区间 \(\gcd\),第二个条件使用 pre
数组处理即可。
代码
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 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
|
维护矩阵
矩阵的大部分运算具有结合律,可以使用线段树维护,从而进行快速的区间查询。例如普通矩阵乘法和各种广义矩阵乘法。
*线段树维护矩阵乘法优化 DP 见 DP 优化。
2025 北京冬令营 B 班模拟赛 2 T1 optimization
题面
给定一个 \(m×n\) 的网格,称 \((i,j)\) 为第 \(i\) (\(1 \leq i \leq m\)) 行第 \(j\) (\(1 \leq j \leq n\)) 列的点。对任意 \((i,j)\),它与 \((i,j−1)\)、\((i,j+1)\)、\((i−1,j)\)、\((i+1,j)\)(如果存在)均有双向边连接,边上有非负权值。
有 \(q\) 次询问,每次询问给定 \(a\), \(b\), \(c\), \(d\),求 \((a,b)\) 和 \((c,d)\) 之间的最短路径的权值。
为了方便叙述,我们记:列的编号为 \(x\),行的编号为 \(y\)。
容易发现一个性质:对于询问 \((sx,sy)\rightarrow(tx,ty)\),其中间的一条竖线 \(x=x_0\)(\(x_0\in[sx,tx]\))中至少有一个节点会被最短路径经过。因此可以考虑分块、分治,但时间复杂度都不行。(我打的分治,\(80pts\))
对于最短路问题,其松弛操作 \(d[u][v]=\min(d[u][k]+d[k][v])\) 和矩阵乘法非常相似。事实上,这正是一种 \((\min,+)\) 运算的广义矩阵乘法。
最短路和广义矩阵乘法
最短路可以转化为 \((\min,+)\) 广义矩阵乘法模型,在一些情况下可以使用矩阵求解。
在思考过程中,很容易想到使用 dp 预处理,低复杂度查询的思路(虽然直接行不通,因为查询区间不是前缀)。再结合矩阵乘法优化 dp 的思路,将 dp 转移方程化为矩阵乘法,就可以使用线段树维护区间 dp 值了。
同时由于 \(m\le 10\) 非常小,可以开 \(m\times m\) 的矩阵。我们可以将矩阵 \(D_{i,j}\) 定义为:从区间左端点 \(l\) 列 \(i\) 行,走到区间右端点 \(r\) 列 \(j\) 行,最短路是多少(如果你考虑过 dp,状态应该就是 类似 这么定义的。当然,最短路可以经过区间之外的点。不过为了简化思路,可以先不考虑这一点)。
注意到这样定义状态有一些很好的性质:比如可以 \(O(m^3)\) 快速合并两个相邻的区间、可直接由区间对应的矩阵得到询问的答案。我们理所应当的使用线段树维护这个矩阵乘法(快速合并 \(\rightarrow\) 线段树)。
接下来考虑两个仍然很重要的问题:如何处理最短路超出区间的情况?边界条件如何处理?
首先,这两个问题并不影响两个区间可以快速合并,以及可以快速查询答案。
既然不影响区间合并,我们不如直接从边界情况入手。我们想要求出每一列的 \(m\) 个点之间的全源最短路 \(d_{i,u,v}\)(最短路可以经过其他列)。我们注意到:经过其他列的路线一定可以被分割成完全在左边的路径和完全在右边的路径。如果将它们分开考虑,然后再进行合并,即可得到每一列的全源最短路。
对于一列点 \(x=x_0\),我们不妨先考虑完全在其左边的路径对 dis_{x_0}
的贡献。我们从左到右依次考虑每一列,并从前一列转移到当前列。注意到每次新考虑一列的点,会加入该列内部的 \(m-1\) 条边,以及和左侧其他列之间的 \(m\) 条边。我们通过后者将 \(dis_{x_0-1}\) 转移到 \(dis_{x_0}\),再加入前者产生的贡献。最后跑 Floyd
松弛,融合两者,即可得到完全在左边的最短路。
我们以相同的方式反方向跑一遍,得到完全在右边的最短路;最后跑 Floyd
松弛,将两者融合,即可得到全局最短路。
当然,我们无需开两个 dis
数组,只需要在第二次跑的时候直接将贡献更新在原先的 dis
上,Floyd
松弛即可。
代码
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 |
|