CSP考完了再来处理这里的内容,只求前4题,CSP的风格应该不会考到这些吧o(╥﹏╥)o
一、可持久化数据结构
可持久化前提:本身的拓扑结构在操作过程中是不改变的
可持久化解决问题
可以存储数据结构的历史版本
核心思想:仅记录每一个版本与前一个版本不同的地方
Trie树的可持久化
假设有字符串cat、rat、cab、fry
,则静态Trie
树:
可持久化Trie
树,图中绿色虚线相连的结点实质上是同一个结点:
// 插入操作
// 旧结点p,开一个新结点q
p = root[i -1], q = root[i] = ++ idx;
// 检查p是否为空,如果是空的,则说明q是一个全新的点,否则q应该是p克隆过来的
if (p) tr[q] = tr[p];
// 更新当前结点新的儿子结点
tr[q][si] = ++ idx;
// 继续往下做,直到将当前单词插入完为止
p = tr[p][si], q = tr[q][si];
线段树的可持久化——主席树
CSP考完了再来听
1.1 最大异或和
构建一个前缀和数组 S i S_i Si,其中 S 0 = 0 S 1 = A 1 S 2 = A 1 ∧ A 2 . . . S i = A 1 ∧ A 2 ∧ . . . ∧ A i \begin{aligned} &S_0 = 0\\ &S_1 = A_1\\ &S_2 = A_1 \land A_2\\ &...\\ &S_i = A_1 \land A_2 \land ... \land A_i\\ \end{aligned} S0=0S1=A1S2=A1∧A2...Si=A1∧A2∧...∧Ai选定一个位置 p p p,那么 A p ∧ . . . ∧ A n ∧ x = S p − 1 ∧ S n ∧ x A_p \land ... \land A_n \land x = S_{p-1} \land S_n \land x Ap∧...∧An∧x=Sp−1∧Sn∧x又因为 S n ∧ x S_n \land x Sn∧x是一个固定值,所以就要求在区间 [ L , R ] [L,R] [L,R]中找到某个位置 p p p,使得 S p − 1 ∧ S n ∧ x S_{p-1} \land S_n \land x Sp−1∧Sn∧x的值最大。
如果是从
[
1
,
R
]
[1,R]
[1,R]中选择某个位置
p
p
p,使得上式的值最大,这里可以使用可持久化的Trie
树来存储每个历史版本的Trie
树,如果查询
[
1
,
R
]
[1,R]
[1,R],只需要查询root[R]
为根节点的这个版本的子树,这里面存的就是
[
1
,
R
]
[1,R]
[1,R]中的每一个数。
如果是从
[
L
,
R
]
[L,R]
[L,R]中选择某个位置
p
p
p,使得上式的值最大。相当于问左边子树中是否至少存在一个数,它的下标>= L
,等价于左子树下标的最大值是否>= L
。所以可以在Trie
树中每个结点使用max_id
记录当前子树中下标的最大值。
- 对是否超过使用空间的估算:空间瓶颈在代码第13行的数组, M × 2 + M = M × 3 = 600000 × 25 × 3 = 1.5 × 1 0 7 × 3 = 4.5 × 1 0 7 M \times 2 + M = M \times 3 = 600000 \times 25 \times 3 = 1.5 \times 10^7 \times 3 = 4.5 \times 10^7 M×2+M=M×3=600000×25×3=1.5×107×3=4.5×107。那么这么大的数据需要 4.5 × 1 0 7 × 4 1 0 6 = 180 M \frac{4.5 \times 10^7 \times 4}{10^6} = 180M 1064.5×107×4=180M,其中数组是
int
即4bite
,分母除以1e6
是将bite
转换为兆M
。这里加上数组 s [ N ] s[N] s[N]的空间 f 600000 × 4 1 0 7 f\frac{600000 \times 4}{10^7} f107600000×4是小于题目要求 256 M 256M 256M的,所以不会爆空间。- 因为
Trie
树是容易爆空间的,如果最后计算出爆了空间,可以使用最大空间250M
来反推,即在空间大小限制250M
下, 最多可以开多少个结点,就开对少个结点。
#include <iostream>
#include <algorithm>
using namespace std;
// 原始数据30w + 操作30w = 序列长度 60w
// Trie中每次操作最多建立24个结点(trie长度24),2^(24 + 根结点) = 2^25 > 1e7
// 一共添加60w次,每次增加25个新结点
const int N = 600010, M = N * 25;
int n, m;
int s[N];
int tr[M][2], max_id[M];
int root[N], idx;
// i为前缀和数组S下标,当前处理到第k位,上一个版本为p,最当前最新版本q
void insert(int i, int k, int p, int q) {
if (k < 0) { // 处理完最后一位
max_id[q] = i; // q就是叶子结点
return;
}
int v = s[i] >> k & 1; // 当前这一位
if (p) // p大于0表示,q是p的复制,将其信息复制过来
tr[q][v ^ 1] = tr[p][v ^ 1];
tr[q][v] = ++idx; // 给当当前这一位开一个新的点
insert(i, k - 1, tr[p][v], tr[q][v]);
max_id[q] = max(max_id[tr[q][0]], max_id[tr[q][1]]); // 更新max_id
}
// 从某个root开始,当前的值c,左边的限制L
int query(int root, int C, int L) {
int p = root;
for (int i = 23; i >= 0; i--) {
int v = C >> i & 1;
if (max_id[tr[p][v ^ 1]] >= L)
p = tr[p][v ^ 1];
else p = tr[p][v];
}
return C ^ s[max_id[p]];
}
int main() {
scanf("%d%d", &n, &m);
max_id[0] = -1; // 根结点的max_id,因为前缀和里面s[0]也是一个合法的值,所以需要一个 <0 的数
root[0] = ++idx;
insert(0, 23, 0, root[0]);
for (int i = 1; i <= n; i++) {
int x;
scanf("%d", &x);
s[i] = s[i - 1] ^ x;
root[i] = ++idx;
insert(i, 23, root[i - 1], root[i]);
}
char op[2];
int l, r, x;
while (m--) {
scanf("%s", op);
if (*op == 'A') {
scanf("%d", &x);
n++;
s[n] = s[n - 1] ^ x;
root[n] = ++idx;
insert(n, 23, root[n - 1], root[n]);
} else {
scanf("%d%d%d", &l, &r, &x);
printf("%d\n", query(root[r - 1], s[n] ^ x, l - 1));
}
}
return 0;
}
1.2 第K小数
CSP考完了再来听
二、平衡树 (Treap = Tree + Heap)
- 二叉搜索树 (BST):
- 当前结点的左子树中的任何一个结点的权值 < < < 当前结点的权值;
- 当前结点的右子树中的任何一个结点的权值 > > > 当前结点的权值;
- BST中一般不存在相同权值的结点,如果存在,则在该结点上使用一个
count
来记录该权值出现次数; - BST的中序遍历是一个从小到大有序序列;
- 作用:动态维护一个有序序列
- 常用操作:
insert、delete、找前驱/后继、找最大\最小、求某个值的排名、求排名为k的数的值、比某个数小的最大值、比某个数大的最小值
- 堆 (Heap),这里特指大根堆。
Treap核心思想:让BST尽可能变得随机。
Node{
int l, r; // 左右儿子
int key, val; // key值BST中排序的k,应该有序,val是堆中的val,应该大于左右儿子的val,即这个树要同时满足BST+堆
}tr[N];
CSP考完了再来听
2.1 普通平衡树
2.2 营业额统计
三、AC自动机
CSP考完了再来听