「Summary」2022-08-04 做题记录
莫队 😍😍😍
2022-08-04
$\mathbb{数颜色}$
确保我真的会带修莫队
其实询问和查询时分开的。需要记录现在的查询是在哪个修改之后的。
每次的修改都是直接暴力计算贡献。
给一个代码框架。
1 | void add(int x) { add contribution } |
$\mathbb{CF940F}$
我不会读题
这个题面真坑,暴力扫就可以过,复杂度在 $\Theta(n ^ {\frac{5}{3}})$ 左右。
$\mathbb{HDU \ 6610}$
能过就怪了
考虑他为什么限制交换操作只操作相邻的两个数字,其实很显然,对于操作后只有 $pos$ 位置上的前缀异或和发生改变,并且可以 $\Theta(1)$ 计算。
这个提示我们把交换操作转化成一个单点修改操作,在读入的时候就可以直接模拟一遍,把操作转过来。之后就是维护区间内相同数字的个数就行了。
关于我写错了哪些地方
1 | void change(int x) { |
综上,写代码不过脑子。
$\mathbb{JOISC 2014 Day1 T3「歴史の研究」}$
算是会写回滚莫队了吧
没什么好解释的,直接分析代码。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25sort(q + 1, q + 1 + m, [](const Query& x, const Query& y) {
if (block[x.l] != block[y.l]) return block[x.l] < block[y.l];
else return x.r < y.r;
});
for (int i = 1, j = 1; j <= (n - 1) / siz + 1; j++) { // 依次遍历每个块
int pos = Min(j * siz, n);
l = pos + 1, r = pos, res = 0;
// 这道题是只增加莫队,所以 左右端点 从空开始扩张
while (block[q[i].l] == j) { // 左端点在块中的查询
if (q[i].r <= pos) { // 右端点也在块中直接暴力算
ans[q[i].ind] = cal(q[i].l, q[i].r);
i++;
continue;
}
while (r < q[i].r) add(++r); // 扩张
LL rec = res; // 此时的左端点还在这个块的最右端此处的 res 可以作为历史版本记录使用
while (l > q[i].l) add(--l); // 扩张
ans[q[i].ind] = res; // 记录答案
while (l <= pos) del(l++); // 把左端点拉回这一块的最右端并撤销贡献
res = rec; // 还原
i++;
}
while (r > pos) del(r--); // 所有清空贡献
}
$\mathbb{Rmq \ Problem}$
依然是回滚
这个是只删除的回滚莫队。
1 | sort(q + 1, q + 1 + m, [](const Query& x, const Query& y) { |
更具体地,回滚莫队工作原理就是如下几步:
- 固定左端点,右端点一直保持单调移动做出贡献
- 记录下此时的贡献作为备份
- 移动左端点回答当前的询问
- 撤销左端点的贡献,并把当前的答案还原成备份
形象一点,就是一个左端点先不动,右端点一直往一个方向动,当右端点碰到询问区间的边界时(*),记录答案
作为备份并挪动左端点到询问区间的左端点进行回答,然后把左端点拉回固定的位置,一路上撤回贡献,回
到起点时,发现当前的状态和(*)是一致的,那么可以直接把答案还原成备份,继续重复以上操作。
$\mathbb{permu}$
线段树乱杀
看到范围小,可以直接普通莫队 + 线段树查询区间最长连续 $1$ 来做,可能要比回滚慢一点,但是能过且好写。
The End「Ô mon âme, n'aspire pas à la vie immortelle, mais épuise le champ du possible.」