202109-5 箱根山岳险天下
前言——建立树模型
题目要求对一个数组进行以下几个操作,强制在线:
- 删除数组中最后一个元素;
- 在数组末尾加入一个元素,注意删掉后又加入的元素与之前元素不能视为相同的元素;
- 在第 \(s\) 次操作后的数组中,将 \([l,r]\) 位置的所有元素乘以 \(y\),这个操作是永久性的;
- 查询在第 \(s\) 次操作后的数组中 \([l,r]\) 位置所有元素目前值的和。
如果知道主席树?
对于历史操作,我们会想到主席树。但使用主席树时会遇到以下问题:
- 强制在线,且操作中存在添加删除元素。主席树不太方便实现。
- 主席树一般只针对最后的数组进行操作,每次操作只会影响到 \(\mathrm{O}(\log n)\) 元素,减小空间占用。但这里的区间乘不是针对最终状态,影响范围很大,空间占用会很多。
考虑用什么样的结构去存储信息。
维护每个操作后的数组
一种朴素的想法是维护每次操作后的数组。
这种做法存在时间问题:对于修改操作,它影响到的范围是 \(\mathrm{O}(m^2)\) 的,总体复杂度 \(\mathrm{O}(m^3)\)。
合并元素
思考浪费时间的地方:
设 \(t\) 时刻 \(x\) 位置的值为 \(A_{x,t}\)。假设目前时刻是 \(t_{cur}\),如果在 \(s\) 时刻修改了 \(x\) 位置的值且直到 \(t_{cur}\) 时刻该元素一直没有被删除,那么影响到的范围是 \(A_{x,s}, A_{x,s+1},\cdots,A_{x, t_{cur}}\) 且操作都是相同的。而题目中所有的查询只针对目前而言,即只需要 \(A_{x, t_{cur}}\)。我们不需要维护中间状态的具体值,只需要知道中间状态的每个元素的位置(排名)。
由此,我们可以只存储每个元素目前时刻的状态,在进行修改时,只针对当前情况修改即可。
查找位置对应元素
由于每个元素只保留了最终状态,我们无法直接确定某时刻某位置的元素。假设要确定在 \(s\) 时刻位置在 \(x\) 的元素,因为一个元素的位置是不变的,我们不妨记录每个位置对应的元素编号与加入时间,那么在 \(s\) 时刻位置子 \(x\) 的元素对应位置在 \(x\) 数组内最后一个加入时间小于等于 \(s\) 的元素。
但是这样也存在问题:要找到一段区间上的所有元素的复杂度是 \(\mathrm{O}(m\log m)\) 的,总体复杂度 \(\mathrm{O}(m^2\log m)\),不太能接受。
转变一下思路,因为增加和删除都是在末端进行,所以在某个元素被删除之前,它前面的元素都是不变的(类似于栈顶元素先变化、栈底元素后变化)。我们不妨将数组视为一条链,那么对应操作:
- 增加一个元素:在链的末端加入元素。
- 删除一个元素:断开最后一个元素与链的连边,由于需要操作不能实际断开,可以通过维护最后一个元素来实现。那么经过该操作后,新的最后一个元素变为原来的倒数第二个元素。
可以看出这是一棵树(准确来说是森林,但可以维护一个位置为 \(0\) 的元素指向所有位置为 \(1\) 的元素,这样就处理成了一棵树),那么要查找一段区间的所有元素,我们可以找到两端,在树上确定了两端也就确定了整条路径。查找两点时间复杂度 \(\mathrm{O}(\log m)\),确定路径复杂度 \(\mathrm{O}(m)\),整体 \(\mathrm{O}(m)\)。
最终模型
用一棵树来维护每个元素的强度,同时记录当前数组的末端元素位置;用数组维护某位置中所有元素的加入时刻与编号,不妨称其为排名数组。
- 增加元素:在当前数组末端元素的节点处增加新的子节点,并将其设为末端元素,同时更新排名数组。
- 删除元素:当前数组末端元素变更为其父亲节点。
- 修改元素:找到对应元素位置,进行链上修改。
- 查询元素:找到对应元素位置,进行链上查询。
测试点 1——搜索确定路径
在建立树模型后,我们暴力进行对应的操作:
- 增加元素:复杂度 \(\mathrm{O}(1)\);
- 删除元素:复杂度 \(\mathrm{O}(1)\);
- 修改元素:确定路径并修改复杂度 \(\mathrm{O}(m)\);
- 查询元素:确定路径并查询复杂度 \(\mathrm{O}(m)\);
整体复杂度 \(\mathrm{O}(m^2)\)。
测试点 2——线段树
由于只有增加没有删除,所以最终形态是一条链。我们维护线段树即可,初始值为 \(0\)。
- 增加元素:线段树单点修改,复杂度 \(\mathrm{O}(\log m)\);
- 修改元素:线段树区间修改,复杂度 \(\mathrm{O}(\log m)\);
- 查询元素:点段数区间查询,复杂度 \(\mathrm{O}(\log m)\);
整体复杂度 \(\mathrm{O}(m\log m)\);
不要求在线——树链剖分
关于树上的区间修改操作一般都可以用树链剖分完成,但树链剖分需要预先知道树的形状,而本题中点都是动态的,无法一开始就得知。如果是离线的话,可以预处理出树的形态,进行剖分。
整体复杂度 \(\mathrm{O}(m\log m)\);
100% 数据——Link Cut Tree
因为有对树的动态加点,所以能看出这是一个动态树问题。解决动态树问题的结构之一就是 LCT。
本题算是 LCT 的板子题,整体复杂度 \(\mathrm{O}(m\log m)\)。
代码实现
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 |
|