数据结构学习笔记
搬运自洛谷博客,原发表时间:2022-07-06
开个坑。慢慢把之前学过的和新学的科技都补充进去。
一些比较板或者无趣的题目就不放了。
CF1446C Xor Tree
题意:给定你一个序列 \(a\),对于每个 \(i\),它会向序列中的满足 \(a_i \ xor \ a_j\) 最小的 \(j\) 连双向边(重边算一条)。现在要让你删去序列中的一些数,使得最后形成的图是一棵树,求最少需要删除几个数。
2100,非常推荐大家尝试。
大致做法:01trie
一个显见的性质是,在所有 \(a_i \ xor \ a_j\) 中最小的,一定连重边。因此可以得知形成一棵树等价于该图连通。
看到异或值最小,想到01trie。考虑在01trie上判断连通性。由于在01trie上,离自己最近的点一定是与自己xor值最小的,因此若一个子树的 \(size \geq 2\),则该子树内的点只可能向该子树中的其他元素连边。
那解法即不难想到了。对01trie做一次dfs,求出最少需要删除几个数即可。
给出dfs的代码如下。
inline int query(int u){
if(sz(ls(u))<=1&&sz(rs(u))<=1)return 0;
int ret=0x3f3f3f3f;
if(ls(u))ret=min(ret,query(ls(u))+max(sz(rs(u))-1,0));
if(rs(u))ret=min(ret,query(rs(u))+max(sz(ls(u))-1,0));
return ret;
}
时间复杂度 \(O(n \log n)\)。
小彩蛋:大家可以去看一看这题的第一篇题解。
P6619 [省选联考 2020 A/B 卷] 冰火战士
题意:有冰火两队,冰队小于等于临界温度的可以上场,火队大于等于临界温度的可以上场。每位队员有一个能量值 \(v\)。设两队上场队员的能量和为 \(S_1,S_2\),则总和 \(S=\min(S_1,S_2) \times 2\)。一次操作向两队中的任一队加入或删除一位队员,在每次操作后求出使 \(S\) 最大的临界温度。
很显然,当临界温度 \(T\) 增大时,\(S_1,S_2\) 分别单调不升、单调不降。因此 \(S\) 必为一个单峰函数,而该峰即为 \(S_1,S_2\) 的交点。
考虑如何维护这个交点。
- 求出最大的 \(T\) 使得 \(S_1(T) < S_2(T)\)。
- 求出最小的 \(T\) 使得 \(S_1(T) \geq S_2(T)\)。
可以想到使用二分求解 \(T\)。
由于需要区间加、前缀和(等价于 \(S_1\) 或 \(S_2\) 的单点查)操作,显然使用树状数组较为合适。
如果直接使用树状数组暴力二分,复杂度为 \(O(n \log^2 n)\),只能获得60分。
也是因为初学树状数组快速二分才特意来做这题。
树状数组的结构为:点 \(i\) 存储 \(i-\text{lowbit}(i)+1\) 到 \(i\) 的和。
使用倍增的思想,从大到小依次枚举这个点加上 \(2^p\),能跳则跳。
这样复杂度为 \(O(n \log n)\),可以通过。
较为卡常,对常数小的实现有较高的要求。
CF1000F One Occurrence
题意:给定一个长度为 \(n\) 的序列,\(m\) 个询问,每次询问给定一个区间 \([l,r]\),输出区间里任意一个只出现一次的数。\(n,m \leq 5 \times 10^5\)。
最直观的想法:莫队。时间复杂度 \(O(n \sqrt{n})\)。(跑的比主席树快!!1)
考虑 \(O(n \times \text{poly}(\log))\) 的做法。
对于每个数,其只出现一次当且仅当:它出现在区间中,且上一次出现在区间外。
因此,每一次询问时看区间中所有数上一次出现位置的最小值,若最小值在区间外,则输出其对应的数即可。
使用主席树维护,时间复杂度 \(O(n \log n)\)。
CF1555E Boring Segments / [NOI2016] 区间
题意:使用一些区间覆盖数轴,求这些区间的权值/长度极差最小值等。
(权值极差最小值)双指针:按权值从小到大排序,右指针右移至能够覆盖整个区间,左指针右移至无法覆盖区间,每次更新答案即可。能否完全覆盖用线段树维护。
区间数 \(n\),数轴长度 \(m\),时间复杂度 \(O(n \log m)\),空间复杂度 \(O(m)\)。
当 \(m\) 较大时需要离散化,时间复杂度 \(O(n \log n)\),空间复杂度 \(O(n)\)。
CF1746F Kazaee
题意:单点修改,区间查询所有数的出现次数是否为 \(k\) 的倍数。
考虑在二进制位上维护。
注意到一个区间中,如果所有数的出现次数为 \(k\) 的倍数,则每个二进制位的出现次数也为 \(k\) 的倍数。
然而,反之不亦然。
考虑随机化算法。
我们将每一个数映射到一个随机数上。由于数的随机性,当随机数的上限为 \(2^S\) 时,错误的概率为 \(\dfrac{1}{k^S}\),特判 \(k=1\) 即可。
对于每一个二进制位,使用树状数组维护。
随机数上限为 \(2^S\) 时,时间复杂度为 \(O(nS \log n)\)。
取 \(S=30\) 时,错误率 \(< 3 \times 10^{-4}\),可以接受。
upd:请使用 mt19937
生成随机数。
P7811 [JRKSJ R2] 你的名字。
题意:区间求模某个数的最小值。
让人想起哈希冲突。
对模数 \(k\) 根号分治。
\(k \le lim\) 时,把 \(k\) 相同的问询放到一起做。随便哪个能维护 RMQ 的数据结构都行。序列分块常数较小,时间复杂度 \(O(n \times lim + m \sqrt n)\)。
\(k > lim\) 时:(\(a\) 为值域)
Sol1
问询相当于 \(\min \{ a_i \ | \ l \le i \le r ,a_i \ge pk \}-pk,\forall p \in N\),将原来的问询拆为 \(m \times \dfrac{a}{lim}\) 个子问题,每个问题相当于求 \(pk\) 的区间不严格后继。
对原数组从大到小排序,并逐个加入。枚举此时的因数,更新原问询的答案。
修改/查询次数是 \(O(a)-O(m \times \dfrac{a}{lim})\) 的,因此我们使用一个建立在分块上的 st 表,并且记录每块的前后缀最小值,\(O(\sqrt n \log n)-O(1)\)。
猫树可以去掉 \(\log n\),可惜我不会。(实际上那个 \(\log n\) 影响很小,但是确实不是最优复杂度)
同时也将空间优化到了 \(O(m)\)。(由于是枚举因数更新原问询,而不是把原问询挂到倍数上)
时间复杂度 \(O(a \sqrt n \log n + m \times \dfrac{a}{lim})\)。
Sol2
莫队,维护一个 \(\text{bitset}\),暴力找最近的数。
时间复杂度 \(O(n \sqrt m + \dfrac{ma}{w} + \dfrac{ma}{lim})\)。(可能有误,我用的sol1,因此没有研究细节)
两种 code 各给一份,分别来自我和 \(\color{black}\text{l}\color{red}\text{gvc}\)。
数据真强。卡不动常数,弃了。