数据结构做题记录
I. P3246 [HNOI2016] 序列
莫队
对于当前区间 \([l,r]\),考虑右端点 \(r\gets r+1\) 的贡献。不难发现,就是 \(\displaystyle \sum_{i=l}^{r+1}\min_{j=i}^{r+1}\{a_j\}\)。
设区间最小值位置为 \(pos\),则 \(a_{pos}\) 会贡献 \((pos-l+1)\) 次,剩下的部分是 \(\displaystyle \sum_{i=pos+1}^{r+1}\sum_{j=i}^{r+1} \min\{a_j\}\)。
发现剩下这部分答案满足可差分性,套路地设 \(\displaystyle f(r)=\sum_{i=1}^r\min_{j=i}^r\{a_j\}\),于是这部分就是 \(f(r+1)-f(pos)\)。
思考如何求出 \(f(i)\)。设在 \(i\) 前面的第一个比 \(i\) 大的数的位置是 \(pre_i\),注意到,\(f(r)=f(pre_r)+(r-pre_r)\cdot a_r\),不难递推得到。
对称地,我们对左端点再次讨论即可。利用朴素的 ST 表维护区间最小值,时间复杂度 \(\Theta(n\log n+m\sqrt n)\),其中 \(\Theta(n\log n)\) 是 ST 表预处理时间。
线段树
TODO。
II. P6604 [HNOI2016] 序列 加强版
设 \([l,r]\) 最小值位置为 \(pos\),它的贡献为 \((r-pos+1)(pos-l+1)\)。然后考虑 \([l,pos)\) 和 \((pos,r]\) 的贡献。
这两部分贡献可以用普通版中莫队的做法中推的式子,具体地说就是考虑端点从 \([pos,pos]\) 推到 \([pos,r]\),除去 \(pos\) 的贡献,剩下那部分就是 \(\displaystyle \sum_{k=pos+1}^{r}\sum_{i=pos+1}^{k}\min_{j=pos+1}^i \{a_j\}\),可以利用 \(f(i)\) 的二维前缀和表达。对于 \([l,pos)\) 的贡献同理。
综上,我们可以 \(\Theta(1)\) 回答每个询问。时间复杂度瓶颈在于朴素 ST 表的预处理,为 \(\Theta(n\log n+q)\)。
根据 Alex_wei 老师的说法,如上做法和 Cartesian 树是有关系的。相关题目:P6503 [COCI2010-2011#3] DIFERENCIJA。
III. #6376. 「HNOI2016」序列 二次加强版
朴素 ST 表无法通过,只能考虑 \(\Theta(n)\sim \Theta(1)\) RMQ。
参见下一篇题解。
IV. P3793 由乃救爷爷
\(\Theta(n)\sim \Theta(1)\) RMQ。
事实证明确定性的分块+ST 表跑得要比非确定性的 Cartesian 树慢得多()
我的实现中,ST 表+分块跑了 11.17s,而 Cartesian 树跑了 4.50s。
算法 1:ST 表+分块(确定性)
参考了 RI 的题解。RI 叫它二毛子()。
考虑将序列分成若干块,每块大小为 \(B=\log n\)。我们对块间做 ST 表,时间复杂度是 \(\displaystyle\Theta\left(\frac{n}{B}\log \frac{n}{B}\right)=\Theta(n)\) 的。
为了处理零散的段,我们预处理块内前缀最大值、块内后缀最大值。额外地,对于两端点在同一段的情况,我们需要额外处理一个 \(val\) 数组,利用二进制压位,维护一个递减的子序列。
例如,\(A=\{1,5,4,3\}\),那么 \(val=\{\{1\},\{2,1\},\{3,2\},\{4,3,2\}\}\)。
对于每次询问,如果两端点在同一块的情况,我们可以直接利用位运算找到最大值。
否则,先处理散块,再 ST 表查询整块。查询时间复杂度 \(\Theta(1)\)。
综上,我们在 \(\Theta(n)\sim \Theta(1)\) 内支持了 RMQ 的查询。
#include <bits/stdc++.h>
using namespace std;
// #define int long long
using ull=unsigned long long;
constexpr int MAXN=2e7+64, B=64;
namespace GenHelper {
unsigned z1,z2,z3,z4,b;
unsigned rand_() { }
}
void srand(unsigned x) { }
int read() { }
int a[MAXN], pre[MAXN], suf[MAXN];
int f[19][MAXN/B+1]; ull val[MAXN];
#define bel(x) ((x+63)>>6)
#define st(x) (((x-1)<<6)|1)
#define ed(x) (x<<6)
int n, m; int stk[MAXN], top;
void build() {
int bnum=bel(n);
for (int i=1; i<=bnum; ++i) {
pre[st(i)]=a[st(i)]; suf[ed(i)]=a[ed(i)];
for (int j=st(i)+1; j<=ed(i); ++j) pre[j]=max(pre[j-1],a[j]);
for (int j=ed(i)-1; j>=st(i); --j) suf[j]=max(suf[j+1],a[j]);
f[0][i]=pre[ed(i)];
}
for (int i=1; i<=__lg(bnum); ++i)
for (int j=1; j+(1<<i)-1<=bnum; ++j)
f[i][j]=max(f[i-1][j],f[i-1][j+(1<<i-1)]);
for (int i=1; i<=bnum; ++i) {
top=0;
for (int j=st(i); j<=ed(i); ++j) {
if (j>st(i)) val[j]=val[j-1];
while (top&&a[j]>=a[stk[top]]) val[j]^=1ull<<stk[top--]-st(i);
val[j]|=1ull<<j-st(i); stk[++top]=j;
}
}
}
int ask(int l, int r) {
int x=bel(l), y=bel(r);
if (x==y) return a[l+__builtin_ctzll(val[r]>>l-st(x))];
int ans=max(suf[l],pre[r]);
if (x+1<y) {
x++, y--;
int t=__lg(y-x+1);
ans=max({ans,f[t][x],f[t][y-(1<<t)+1]});
}
return ans;
}
ull ans=0;
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr); cout.tie(nullptr);
unsigned s;
cin>>n>>m>>s; srand(s);
for (int i=1; i<=n; ++i) a[i]=read();
build();
int l, r;
while (m--) {
l=read()%n+1, r=read()%n+1; if (l>r) swap(l,r);
ans+=ask(l,r);
}
cout<<ans;
}
算法 2:Cartesian 树(依赖随机数据)
我们 \(\Theta(n)\) 建出 Cartesian 树。
我们知道,RMQ 问题可以转为 Cartesian 树上的 LCA 问题。本题中,由于数据随机,所以直接暴力跳 LCA 即可。时间复杂度期望 \(\Theta(1)\)。
V. [AGC028B] Removing Blocks
VI. P5443 [APIO2019] 桥梁
首先,在 \(i\) 号灯变量的时刻,我们设包含 \(i\) 的极长连通段为 \([l,i]\),包含 \(i+1\) 的极长联通段为 \([i+1,r]\),那么 \(\forall s\in [l,i],t\in [i+1,r]\),都被连通了。发现我们可以对 \([l,i],[i+1,r]\) 做一个矩形加。
VII. P4314 CPU 监控
TODO。
VIII. P6242 【模板】线段树 3
IX. CF997E Good Subsegments
考虑套路地进行扫描线。
X. P5046 [Ynoi2019 模拟赛] Yuno loves sqrt technology I
XI. P5298 [PKUWC2018] Minimax
XII. P5048 [Ynoi2019 模拟赛] Yuno loves sqrt technology III
XIII. P6684 [BalticOI 2020 Day1] 小丑
XIV. P3247 [HNOI2016] 最小公倍数
操作分块+并查集。
具体地,将边按照 \(a\) 分块,块内按照 \(b\) 排序;
将询问对 \(b\) 排序,然后按照 \(a\) 挂到块中。
对于整块,由于我们已经按照 \(b\) 排序,所以直接处理即可;对于散块,暴力加入后撤销即可。
记块大小为 \(B\),时间复杂度 \(\Theta\left(qB\log B+Bm\log m\right)\)。取 \(B=\sqrt {m\log m}\) 即可。
XV. P5072 [Ynoi2015] 盼君勿忘
XVI. P2479 [SDOI2010] 捉迷藏
XVII. P5324 [BJOI2019] 删数
XVIII. P4513 小白逛公园
线段树维护区间最大子段和的板子。
XIX. P5327 [ZJOI2019] 语言
TODO。
XX. P5012 水の数列
TODO。
XXI. P5283 [十二省联考 2019] 异或粽子
转为异或前缀和是平凡的。
Solution 1:Trie
不妨将 \(k\) 乘以 \(2\),转为无序的问题。
我们维护四元组 \((i,k,v)\),表示与 \(\mathrm{sum}_i\) 异或,第 \(k\) 大的值为 \(v\)。\(v\) 不难利用 Trie 上二分(考虑类似平衡树的二分方式)在 \(\Theta(\log V)\) 内找出。
于是,平凡地,我们对于 \(\forall i\in [0,n]\),将 \((i,1,v)\) 插入大根堆中。每次取出最大的值 \((i,k,v)\) 后,将 \((i,k+1,v')\) 压入堆中。
时间复杂度 \(\Theta((n+k)\log V)\),时空常数明显优于解法 2。
Solution 2:持久化 Trie
类比 XXIX 超级钢琴 的套路,维护五元组 \((st,l,r,pos,v)\) 表示 \([l,r]\) 区间内,与 \(\mathrm{sum}_{st}\) 异或值最大的位置是 \(pos\),最大的值是 \(v\)。
\(pos\) 不难利用持久化 Trie 求出。每次取出 \((st,l,r,pos,v)\) 后,我们将 \((st,l,pos-1,pos',v')\) 和 \((st,pos+1,r,pos'',v'')\) 压入堆中即可。
时间复杂度 \(\Theta((n+k)\log V)\)。
XXII. CF125E MST Company
经典结论:设点集内 dfs 序最小和最大的点分别为 \(u,v\),则点集的 \(\mathrm{LCA}\) 为 \(\mathrm{lca}(u,v)\)。
于是维护区间最大/次大/最小/次小 dfs 序即可。时间复杂度 \(\Theta(m\log n)\)。
XXIII. P5926 [JSOI2009] 面试的考验
这是三倍经验的第一题,我们考虑利用分块在 \(\Theta(n+m)\sqrt n\) 的时间复杂度内解决。
XXIV. CF765F Souvenirs
这是三倍经验的第二题,我们考虑利用权值线段树在 \(\Theta(n\log^2 V)\) 的时间复杂度内解决。
XXV. CF1793F Rebrending
这是三倍经验的第三题,我们考虑利用根号分治在 \(\Theta(n\sqrt n+m)\) 的时间复杂度内解决。
XXVI. UVA1619 感觉不错 Feel Good
XXVII. [ABC311G] One More Grid Task
XXVIII. P2839 [国家集训队] middle
XXIX. P2048 [NOI2010] 超级钢琴
XXX. CF1192B Dynamic Diameter
考虑利用线段树维护 Euler 序。
不妨设 \(st_u\) 为 \(u\) 第一次出现的位置。
引理 1 \(u,v\) 的 LCA 是 \([st_u,st_v]\) 中深度最小的点。
引理 2 边权非负的时候,直径长度即为
\[\max_{u,v} \left\{\mathrm{dep}_u+\mathrm{dep}_v-2\mathrm{dep}_{\mathrm{lca}(u,v)}\right\} \]
也就是说,我们的答案就是
这启示我们,在线段树的节点上维护:
- \(\mathrm{mx}_1=\max\left\{\mathrm{dep}_u\right\}\);
- \(\mathrm{mn}_1=\min\left\{\mathrm{dep}_u\right\}\);
- \(\mathrm{lmx}=\max_{u\le v}\left\{\mathrm{dep}_u-2\mathrm{dep}_v\right\}\);
- \(\mathrm{rmx}=\max_{v\le u}\left\{\mathrm{dep}_u-2\mathrm{dep}_v\right\}\);
- \(\mathrm{ans}=\max_{u,v} \left\{\mathrm{dep}_u+\mathrm{dep}_v-2\mathrm{dep}_{\mathrm{lca}(u,v)}\right\}\)。
考虑合并两个节点 \(l,r\)。
\(\mathrm{mx}_1,\mathrm{mn}_1\) 的合并是平凡的。
\(\mathrm{lmx},\mathrm{rmx}\) 的合并
直接取四种情况的 \(\max\) 即可,是不难的。
\(\mathrm{ans}\) 的合并
取
- \(\mathrm{ans}_l,\mathrm{ans}_r\),
- \(\mathrm{lmx}+\mathrm{mx}_2\),
- \(\mathrm{rmx}+\mathrm{mx}_1\)
的 \(\max\) 即可。
考虑如何修改。对应区间上,我们有
- \(\mathrm{mx}_1\gets\mathrm{mx}_1+\Delta\);
- \(\mathrm{mn}_1\gets\mathrm{mn}_1+\Delta\);
- \(\mathrm{lmx}\gets\mathrm{lmx}-\Delta\);
- \(\mathrm{rmx}\gets\mathrm{rmx}-\Delta\)。
加上 tag 后直接做就可以了。
时间复杂度是 \(\Theta((n+q)\log n)\) 的。
XXXI. P8078 [WC2022] 秃子酋长
莫队的 \(\Theta(n\sqrt {m\log n})\) 是平凡的。由于 \(n,m\leq 5\times 10^5\),无法通过。我们考虑 \(\Theta(n\sqrt m)\) 的做法。
考虑排列的逆排列。不难发现,时间复杂度的瓶颈在于插入,如果没有插入只有删除的话,可以利用双向链表做到 \(\Theta(1)\)。
那么考虑不插入的回顾莫队:
首先建出整个序列对应的双向链表。
- 将询问按照左端点所在块编号为第一关键字(升序),右端点为第二关键字降序排序;
- 左端点处理到块 \([st,ed]\) 的时候,将 \([1,st)\) 在双向链表中删除;
- 记录此时的状态(此时右端点在 \(n\) 处),处理询问:
- 将右端点移动到询问的右端点 \(R\);
- 将左端点移动到询问的左端点 \(L\),回答询问;
- 撤销左端点的移动,将左端点移回到 \(st\)。
- 处理完 \([st,ed]\) 的询问后,将 3 中记录的状态还原,回到 3 处理下一块。
时间复杂度 \(\Theta(n\sqrt m)\)。本题对常数要求较高,需要精细实现。
XXXII. [ABC244Ex] Linear Maximization
对于 \(b=0\) 的情况,直接用 \(\texttt{std::set}\) 维护即可。
否则,我们利用一棵李超线段树维护。具体地说,注意到
就是求一些一次函数 \(y=kx+b\)(\(k=x,b=y\))在点 \(\dfrac{a}{b}\) 处的最值。我们利用李超线段树维护这些分数即可。
时间复杂度 \(\Theta(n\log n)\)。
本题还有利用凸包的解法,详见 XXVIII。
XXXIII. P3309 [SDOI2014] 向量集
答案显然在凸包上。由于涉及区间询问,所以考虑线段树。
具体地说,我们在线段树的每个节点上维护一个凸包。查询时,在 \(\Theta(\log n)\) 个节点上二分即可,总时间复杂度是 \(\Theta((n+q)\log^2 n)\) 的,吗?
考虑插入。如果每次插入我们都 \(\Theta(n\log n)\) 暴力重构凸包,显然时间复杂度不正确。
但是可以在一个区间刚好满的时刻(也就是,加入的元素使得这个区间被填满)建立凸包,这样时间复杂度就正确了。正确性显然。
现在是 18:29,我看看我什么时候写完。
其实还是很好写的。code.
XXXIV. P5631 最小 mex 生成树
我们有两种解法。
整体二分
暴力的做:将边权等于 \(x\) 的边删掉,看可不可行。
考虑整体二分,\(solve(l,r)\) 表示将边权 \(\in [l,r]\) 的边删掉,可不可行。
时间复杂度 \(\Theta(V\log V\log n)\)。
线段树分治
在边权上建立线段树,将一条边 \((u,v,w)\) 拆成 \((u,v,[0,w))\) 和 \((u,v,(w,+\infty))\)。然后利用可撤销并查集维护即可,时间复杂度 \(\Theta(V\log V\log n)\)。
XXXV. SP9576 Dynamic Graph Connectivity
XXXVI. P3899 [湖南集训] 更为厉害
XXXVII. P5227 [AHOI2013] 连通图
XXXVIII. CF1628E Groceries in Meteor Town
XXXIX. [ABC295G] Minimum Reachable City
XL. P6089 [JSOI2015] 非诚勿扰
XLI. [ABC342G] Retroactive Range Chmax
XLII. P3521 [POI2011] Tree Rotations 旋转树木
XLIII. [ABC341G] Highest Ratio
XLIV. P4093 [HEOI2016/TJOI2016] 序列
XLV. P4848 崂山白花蛇草水
XLVI. P5445 [APIO2019] 路灯
XLVII. P5689 [CSP-S2019 江西] 多叉堆
XLVIII. P10590 磁力块
IL. P10589 楼兰图腾
L. P3332 [ZJOI2013] K大数查询
LI. P2617 Dynamic Rankings
LII. P4175 [CTSC2008] 网络管理
首先套路地整体二分。
LII. P3242 [HNOI2015] 接水果
首先套路地整体二分。
考虑刻画路径 \((u,v)\) 被 \((s,t)\) 包含的条件。设 \(\mathrm{dfn}(u)\le \mathrm{dfn}(v),\mathrm{dfn}(s)\le \mathrm{dfn}(t)\)。
- \(\mathrm{LCA}(u,v)\ne u\)
那么,只需要 \(s\) 在 \(u\) 子树内,\(t\) 在 \(v\) 子树内就好了。
即,\(\mathrm{dfn}(s)\in [\mathrm{dfn}(u),\mathrm{dfn}(u)+\mathrm{siz}(u)-1]\),\(\mathrm{dfn}(t)\in [\mathrm{dfn}(v),\mathrm{dfn}(v)+\mathrm{siz}(v)-1]\)。
- \(\mathrm{LCA}(u,v)= u\)
设 \(u\) 的儿子中,\(v\) 的祖先为 \(p\)。
那么,只需要 \(s\) 不在 \(p\) 子树内,\(t\) 在 \(v\) 子树内就好了。
即,
- \(\mathrm{dfn}(s)\in [1,\mathrm{dfn}(p)-1]\),\(\mathrm{dfn}(t)\in [\mathrm{dfn}(v),\mathrm{dfn}(v)+\mathrm{siz}(v)-1]\)。
- \(\mathrm{dfn}(s)\in [\mathrm{dfn}(v),\mathrm{dfn}(v)+\mathrm{siz}(v)-1]\),\(\mathrm{dfn}(t)\in [\mathrm{dfn}(p)+\mathrm{siz}(p),n]\)。
将路径 \((s,t)\) 转化为二维平面上的点 \((\mathrm{dfn}(s),\mathrm{dfn}(t))\)。对于每个盘子 \((u,v)\),做矩形加,然后对于每个水果做单点查即可。
最后,扫描线即可。时间复杂度 \(\Theta((p+q)\log^2 n)\)。
LIII. P5344 【XR-1】逛森林
LIV. P5025 [SNOI2017] 炸弹
LV. [Nikkei 2019 Final F] Flights
题解。
LVI. P5471 [NOI2019] 弹跳
LVII. P3709 大爷的字符串题
直接回滚莫队即可,时间复杂度 \(\Theta(m\sqrt n)\)。
LVIII. P1997 faebdc 的烦恼
LIX. P10638 BZOJ4355 Play with sequence
第二个操作可以看成是区间加与区间 chmax 的复合。于是只需要维护区间覆盖、区间加、区间 chmax 这三个操作就好了。
对于区间 chmax,我们用 segbeats 的套路,具体地说:
维护区间次小值 \(\mathrm{sec}\) 和区间最小值 \(\mathrm{mn}\),设 chmax 的值是 \(v\)。当当前区间完全包含询问区间时:
- \(v\le \mathrm{mn}\):无事可做,直接返回;
- \(\mathrm{mn}\lt v\lt \mathrm{sec}\):直接修改 \(\mathrm{mn}\) 即可;
- \(\mathrm{sec} \le v\):递归处理。
吉如一老师证明了这么做的时间复杂度是 \(\Theta(m\log^2 n)\) 的。
区间加和区间 chmax 是平凡的。只需要再维护一个区间最小值的数量 \(\mathrm{cnt}\),查询的时候就是查询 \(\sum \mathrm{cnt}\cdot [\mathrm{mn}=0]\)。
需要注意 tag 下传的顺序:先下传区间覆盖,再下传区间加,最后区间 chmax。区间覆盖之后要把区间加的 tag 清除掉。
似乎还有一种做法就是设 tag \((a,b)\) 为 \(x\gets \max(x+a,b)\),然后注意到这个东西是满足结合律的。
代码。
LX. CF526F Pudding Monsters
LXI. CF997E Good Subsegments
LXII. P4462 [CQOI2018] 异或序列 / IL. CF617E XOR and Favorite Number
/fn
直接莫队即可。时间复杂度 \(\Theta(m\sqrt n)\)。
LXIII. P9631 [ICPC2020 Nanjing R] Just Another Game of Stones
第一问可以直接上吉司机线段树。
LXIV. P10639 BZOJ4695 最佳女选手
Segbeats 板子题。我们维护区间的
- \(\mathrm{mx},\mathrm{secmx},\mathrm{mxcnt}\):最大值,严格次大值,最大值的个数;
- \(\mathrm{mn},\mathrm{secmn},\mathrm{mncnt}\):最小值,严格次小值,最小值的个数;
- \(\mathrm{sum}\):区间和。
以区间 chmax \(v\) 为例,当当前区间完全包含询问区间时:
- \(v\le \mathrm{mn}\):无事可做,直接返回;
- \(\mathrm{mn}\lt v\lt \mathrm{secmn}\):直接修改 \(\mathrm{mn}\) 即可;
- \(\mathrm{secmn} \le v\):递归处理。
chmin 同理,对于 add 操作只需要维护一个 lazytag 即可。
需要注意的是,本题中同时有 chmax 和 chmin 操作,对于区间内只有 1~2 个本质不同的数的时候,需要特别处理一下。
同时需要注意 pushdown 时各个 tag 的顺序。
然后就做完了,代码很好写。时间复杂度可以证明是 \(\Theta(m\log^2 n)\) 的。
LXV. P5354 [Ynoi2017] 由乃的 OJ
首先,\(\Theta(m\log^2 n\log V)\) 的做法是显然的,但是过不去。但是你发现可以用 ull \(\Theta(1)\) 信息合并,就做完了。
LXVI. P10637 BZOJ4262 Sum
TODO。
LXV. [AGC014E] Blue and Red Tree
LXVI. P3604 美好的每一天
套路地状态压缩,求一遍异或前缀和,问题转化为:
求满足 \(l-1\le i\lt j\le r\),且 \(a_i\oplus a_j=2^k\) 或 \(=0\) 的 \((i,j)\) 数量。
显然可以用莫队。时间复杂度 \(\Theta(m\sqrt n\log V)\)。
LXVII. [ARC122D] XOR Game
LXVIII. P2468 [SDOI2010] 粟粟的书架
经典二合一。
对于前 \(50\%\) 的数据,考虑整体二分,然后用二维 BIT 维护,这样是 \(\Theta(m\log V\log^2 n)\) 的。
对于剩下的数据,考虑持久化线段树。这样是 \(\Theta(m\log n\log V)\) 的。
LVIII. P4559 [JSOI2018] 列队
经典结论是,将学生升序排序后,依次匹配 \(K,K+1,\cdots,K+r-l\) 就是最优答案。
考虑怎么求答案。绝对值看起来很难受,我们不妨分成几段考虑。
对于 \([1,K)\) 这个区间,答案就是 \(\displaystyle \frac{cnt(cnt+1)}{2}+K\cdot cnt-sum\);对于 \((K+r-l,N]\) 这个区间,答案就是上式的相反数,这两段是好求的。
接下来就是 \([K,K+r-l]\) 这段区间内的贡献了。二分出从哪开始,学生休息的位置超过列队的位置,然后算贡献即可。
时间复杂度 \(\Theta(m\log V)\)。