数据结构做题记录
数据结构做题记录
\(\text{I}\). luoguP5142 区间方差
容易得到
线段树维护出 \(\sum a_i\) 和 \(\sum a_i^2\) 即可。
\(\text{II}\). CF739C Alyona and towers
考虑现将原数组差分,则可以将题目转换为求一个区间满足先是一串正数,再是一串负数。
而这个可以用类似最大子段和的方法维护,即对于每个区间维护最大长度,从左边开始的最大长度和从右边开始的最大长度。
\(\text{III}\). [JSOI2018] 列队
将一些数变成连续的数可能不太好处理。于是按套路减去它的排名,然后变成相同的数。
具体地,有
其中 \(rk_i\) 表示 \(a_i\) 在 \(a_{[l,r]}\) 内的排名。
为方便先将 k--
。
按 \(a_i\) 从小到大考虑每个 \(i\),因为 \(a_i\) 互不相同,所以 \(a_i\) 的增长一定不会慢于 \(rk_i\) 的增长。于是满足 \(a_i-rk_i-k\ge0\) 的一定是一段后缀。
考虑在主席树上二分出分界点,然后就可以 \(O(1)\) 计算了。
时间复杂度 \(O(n\log n)\)。
\(\text{IV}\). luoguP3730 曼哈顿交易
直接暴力莫队可以用平衡树求出第 \(k\) 小,时间复杂度为 \(O(n\sqrt n\log n)\)。但毒瘤出题人显然不想要它通过。
由于不可差分,不能二次离线,只能考虑用 \(O(1)-O(\sqrt n)\) 的分块。
对值域分块,维护出每个值的出现次数和每个块内所有值的总出现次数。查询时先跳整块,再跳散块即可。
时间复杂度 \(O(n\sqrt n)\)。
\(\text{V}\). *luoguP7708 八云蓝自动机 Ⅰ
带修莫队可以做到 \(O(n^\frac{5}{3})\),很可惜不能通过。
首先单点修改可以新建一个新点变成交换操作。
考虑在莫队时维护以下信息:
-
现序列的状态 \(a\)。
-
现位置 \(i\) 上的数的原位置 \(rev_i\)。
-
原位置 \(i\) 上的数的现位置 \(pos_i\)。
-
现序列上位置 \(i\) 被询问的次数 \(add_i\)。
当右端点右移 \(r-1\to r\) 时:
-
若 \(r\) 是交换 \(x,y\),直接交换 \(a_x,a_y\),\(rev_x,rev_y\),\(pos_{rev_x},pos_{rev_y}\),\(add_x,add_y\)。
-
若 \(r\) 是询问 \(x\),令 \(ans\leftarrow ans+a_x\),\(add_x\leftarrow add_x+1\)。
当左端点左移 \(l+1\to l\) 时:
-
若 \(l\) 是交换 \(x,y\),直接交换 \(a_{pos_x},a_{pos,y}\),\(rev_{pos_x},rev_{pos_y}\),\(pos_x,pos_y\)。然后答案加上交换后的 \((a_{pos_x}-a_{pos_y})(add_{pos_x}-add_{pos_y})\)。
-
若 \(l\) 是询问 \(x\),令 \(ans\leftarrow ans+a_{pos_x}\),\(add_{pos_x}\leftarrow add_{pow_x}+1\)。
剩余两种情况是类似的,只不过符号不同。
时间复杂度 \(O(n\sqrt n)\)。
\(\text{VI}\). *[APIO2019] 路灯
点亮路灯相当于连接 \((i,i+1)\),熄灭路灯相当于断开 \((i,i+1)\)。
考虑维护出每个点所属的连通块,以及该连通块的左右端点。那么每次修改相当于区间赋值,可以用 ODT 实现。
记 \(bel_i\) 表示 \(i\) 所在的连通块,\(l_i,r_i\) 为第 \(i\) 个连通块的左右端点。那么点亮路灯就会使 \([l_{bel_i},i]\) 与 \([i+1,r_{bel_{i+1}}]\) 联通。
使用提前计算贡献的方法。点亮是给 \([l_{bel_i},i]\) 到 \([i+1,r_{bel_{i+1}}]\) 的贡献加上 \(q-T\)(\(T\) 为当前时间),熄灭时就减去。注意到这是一个动态矩形加,单点查的操作,可以用 cdq 分治+扫描线或树套树解决。
\(\text{VII}\). [POI2011] MET-Meteors
先考虑对每个国家怎么做。
可以想到二分,判断是可以将前 \(mid\) 个询问的操作都加上贡献。
但这样太浪费了,于是可以整体二分再用树状数组维护。
\(\text{VIII}\). *[Ynoi2006] rldcot
由于 \(\operatorname{lca}\) 总共就只有 \(n\) 个,所以肯定可以找支配对。
套路的树上启发式合并,每次加入一个点时找到它的前驱和后继,这样肯定支配了其他点。因为每个点只会加入 \(O(\log n)\) 次。所以这样有 \(O(n\log n)\) 个支配对。
若把一组询问看作一个点 \((l,r)\),那么每个支配对的贡献就是矩形操作。考虑把 \(dep(\operatorname{lca}(i,j))\) 相同的支配对放到一起,那么就要求一些 2-side 矩形的并。
这可以经典的转化为 4-side 矩形加问题,然后用扫描线+树状数组解决。
时间复杂度 \(O(n\log^2+q\log n)\)。
\(\text{IX}\). *Luogu P7811 [JRKSJ R2] 你的名字
考虑根号分治:
-
若 \(k\le B\),直接暴力求出 \(a_i\bmod k\) 的值后做 RMQ。离线逐块处理可以做到线性空间,而时间是 \(O(nB+q\log n)\) 的。
-
若 \(k>B\),则枚举每一个 \(ik\),那么答案就是所有 \(ik\) 的后继模上 \(k\) 的值中最小的那个。用主席树可以做到 \(O(\frac{qV}{B}\log n)\),但不能通过。
考虑将问题规约到求区间 \([l,r]\) 内 \(x\) 的后继。
考虑按 \(x\) 从大到小扫描,则问题变为带修 RMQ。注意到修改只有 \(O(n)\) 次,而查询有 \(O(\frac{qV}{B})\) 次,于是直接上底层分块的猫树即可。
具体的,先将序列分块后再建猫树。每次修改时就重构所在块的信息并且重构猫树,查询是就直接在猫树上查。这样就可以做到 \(O(\sqrt n)-O(1)\) 的带修 RMQ。
时间复杂度 \(O(n\sqrt n+\frac{qV}{B})\)。
所以总时间复杂度为 \(O(n\sqrt n+q\log n+nB+\frac{qV}{B})\)。当 \(B=\sqrt{\frac{qV}{n}}\) 时取到最优时间复杂度 \(O(n\sqrt n+q\log n+\sqrt{nqV})\)。
有亿点卡常。
\(\text{X}\). CF793F Julia the snail
可以发现一次询问走的每条绳子的下端是单调不降的,因为如果要走一次下降的,就可以回溯。
于是从小到大按 \(y\) 扫描,并维护每个点作为 \(x\) 的答案 \(f(i)\)。那么一条绳子 \((l_j,r_j)\) 就相当于把 \([1,l_j]\) 中满足 \(f(i)\ge l_j\) 的 \(f(i)\) 修改为 \(r_j\)。
这可以直接用势能线段树做,若不用改或只用改最大值就打标记,否则就递归下去。因为每次递归说明这段区间内值的个数至少会减一,而初始时最多有 \(O(n\log n)\) 个不同的值,所以时间复杂度是 \(O(n\log n)\) 的。
\(\text{XI}\). LuoguP6072 『MdOI R1』Path
先把 \(f(E(x,y))\) 变成 \(d_x\oplus d_y\),其中 \(d_x\) 为 \(x\) 到根路径上边权的异或和。
注意到任意两条不交路径一定存在一个点,使得其中一条路径的两个端点都在其子树内,另一棵树的两个端点都在其子树外。证明直接取某条路径两个端点的 \(\operatorname{lca}\) 即可。
于是可以枚举那个点 \(u\),现在问题变成了询问点 \(u\) 子树内/外任选两点的 \(d\) 的异或和的最大值。
子树内是好求的,可以直接树上启发式合并,然后用 Trie 树查询最大异或和。
子树外就考虑找到 \(p,q\) 使得 \(d_p\oplus d_q\) 最大。那么不在 \(p,q\) 到根路径上的点的答案就是 \(d_p\oplus d_q\)。
否则就是查询一条链。这样每个点只会被加入一次,也是用 Trie 树查询最大异或和。
时间复杂度 \(O(n\log^2n)\)。
\(\text{XII}\). [TJOI2018] 异或
水题。直接树剖拆成 \(O(q\log n)\) 个区间,然后用可持久化 Trie 维护即可。
时间复杂度 \(O(q\log n\log V)\)。
\(\text{XIII}\). *[Ynoi2008] rrusq
考虑对矩阵扫描线,每加入一个矩阵就将其内部的点的标记改为该矩阵的编号。那么询问就变成了问标记 \(\ge l\) 的点的点权和。
考虑如何实现二维区间修改的操作,可以直接上 KD-Tree。每次打新标记时就将其子树内的标记清空。这样每个标记只会被加入一次,删除一次,时间复杂度仍为 \(O(q\sqrt n)\)。
但这样就会有 \(O(q\sqrt n)\) 次修改,\(O(q)\) 次查询后缀和,直接用 \(O(1)-O(\sqrt n)\) 的分块就可以做到 \(O(q\sqrt n)\)。
\(\text{XIV}\). *[KTSC 2025] 军事基地
考虑按横坐标扫描,那么需要加删线段,和合并有交线段的编号。
注意到两个线段有交当且仅当其在线段树上拆分得到的点具有祖先后代关系。
处理与祖先的合并可以暴力合并。
而处理与子树内的合并可以在这个点打上标记,而删除一条线段时就要于祖先上的标记合并。因为标记合并后可以只保留一个,所以可以按删除时间维护单调栈。每次弹出若干个后只需加入删除时间最晚的那个。
由于一些奇怪原因,开 stack
会爆空间,要开 vector
。
\(\text{XV}\). P11956 「ZHQOI R1」树图
水题。
因为与每个点相连的边中权值最大的那条一定会在最小生成树中,所以答案为所有点到直径两端距离的最大值。因为直径会被算多算一次,所以要减去直径长度。
考虑加入一个叶子后的变化,若变化说明新的直径的一个端点为这个叶子。假设直径端点为 \(u,v\)(其中 \(u\) 为新加的叶子),那么容易找到点 \(x\) 作为到 \(u,v\) 距离哪个远的分界线。
考虑用线段树维护每个点到直径端点距离的最大值,那么修改就是区间加一。
\(\text{XVI}\). [MX-X10-T5] Masuko or Haru?
先对序列差分,把修改操作改为修改两个点。
套路地给两个点连边,那么发现操作一条路径上的边就会将路径的两个端点反转。
可以发现只用关心两个点是否联通,所以只用保留一棵生成森林。
注意到对于每个森林,除了根节点的值都可以是任意的。具体操作方法就是从叶子开始调整。于是就把非根节点的值都强制改为 1
,然后再判断两个串是否相等。因为最多只会加 \(m-1\) 条边,所以均摊是 \(O(nm)\) 的。
判断相等直接用字符串哈希。
\(\text{XVII}\). [THUSC 2021] 搬东西
考虑如何求出搬走的物品集合。
二分最小的物品的编号,并判断是否满足条件,这部分可以用主席树做到 \(O(n\log^2n)\)。
但还要支持删除操作,所以在外层套上一个树状数组就可以做到 \(O(n\log^3n)\) 了。
\(\text{XVIII}\). [THUSC 2021] 白兰地厅的西瓜
考虑设 \(f_u,g_u\) 表示以 \(u\) 为起点且终点在 \(u\) 子树内的最长上升/下降子序列长度。转移可以用线段树合并维护子树内的 \(f/g\)。
然后考虑每次加入一棵子树对答案的贡献,分为两种情况:
-
若序列包含点 \(u\),那么可以直接拼起来。
-
否则,就需要在线段树合并时使用 cdq 分治的思想。具体来说,就是考虑左区间的 \(f\) 对右区间的 \(g\) 的贡献。