关于分块 / 莫队的一些想法 / 拓展

听说 \(\texttt{Luogu}\) 要改博客域名了,所以赶紧把一些精髓搬过来。

动态树模板

个人认为非常不实用,所以就隐藏了。

树套树模板

题目链接 : \(\texttt{二逼平衡树}\)

我们设一个 \(times_{i,j}\) 为第 \(i\) 块,权值为 \(j\) 的个数。设 \(fock_{i,j}\) 代表 \(1\)~\(i\) 块权值为 \(j\) 的个数。设 \(block_{i,j}\) 代表 \(1\)~\(i\) 块,权值在第 \(j\) 块的个数,同未来日记。查询的时候我们直接搞一搞,修改我们也搞一搞就好了。

查询 \(k\) 的排名,只需要对边角暴力一下,然后用 \(block\) 统计答案 (要对本块再次进行暴力)。运算次数 \(3 \times \sqrt{N}+2 \times \sqrt{val}\)
查询排名为 \(k\) 的数字,我们再次把边角加起来,然后同 \(block\) 一起统计答案。运算次数 \(3 \times \sqrt{N}+2 \times \sqrt{val}\)
单点修改,我们把 \(times\) 改一下,然后对后面的 \(fork\)\(block\) 进行实时维护。运算次数 \(\sqrt{N}\)
\(k\) 的前继,我们把边角加起来,然后看 \(k\) 这个块有没有前继,没有用 \(block\) 往前跳,后继同理。运算次数 \(3 \times \sqrt{N}+2 \times \sqrt{val}\)

全部的时间复杂度都在 \(\sqrt{}\) 以内,比第一个题解可谓是快了不少。且常数可以大大优化。

代码坑 (还没有改出来)

天降之物

题目链接 : \(\texttt{天降之物}\)

个人赶脚 \(\texttt{foreverlastnig}\) 大佬的博客非常值得借鉴,因为我的方法的空间很可能被卡。

由于答案只能由左右两边的点与自己构成,意思就是一个 \(x\) 和一个 \(y\),如果它们之间存在着 \(x_1\),那么对答案产生贡献的就只有 \((x_1,y)\) 而不可能是 \((x,y)\) , \(dis(x,y)\) 一定大于 \(dis(x_1,y)\)

考虑分块,记录一个 \(answer_{i,x,y}\) 代表第 \(i\)\(x\)\(y\) 的答案,动态空间可以做到 \(N\sqrt{N}\)。然后记录一个 \(dis_{i,x,0/1}\) 代表第 \(i\) 块最左边 (或者右边) 离块的边缘的距离是多少。前者先记录单独每一个块的答案,后者用来记录跨块的答案贡献。当然,如果你觉得自己的空间写得十分的小的话,你可以尝试搞两个分块,那就不用后者了。

但是考虑到 \(answer_{i,x,y}\) 的空间需要动态,所以需要一个 \(number_{i,x}\) 记录第 \(i\)\(x\) 的编号,然后还要开一个队列来维护这些编号,空间常数巨大,我们的块中点的个数只能设成 \(170\) 个。那么我们的理想预算次数就变成了 \(58823529\),加上预处理就很容易炸。这个时候就要 \(\text{O}_2\) 帮忙了。\(\texttt{Pascal}\) 选手 (比如我) 就要自己去想想怎么优化。

未来日记

题目链接 : \(\texttt{未来日记}\)

此题并不难。

首先我们设一个 \(times_{i,j}\) 为第 \(i\) 块,权值为 \(j\) 的个数。设 \(fock_{i,j}\) 代表 \(1\)~\(i\) 块权值为 \(j\) 的个数。设 \(block_{i,j}\) 代表 \(1\)~\(i\) 块,权值在第 \(j\) 块的个数。满足 \(times_{i,j}\) 实时更新,且一次我们只触及 \(x,y\) 两个数,等于单点修改,所以只需要 \(\sqrt{N}\) 的时间就可以更新 \(fock_{i,j}\),\(block_{i,j}\) 同理。

其次,我们对大块进行修改的时候,直接一个并查集即可。不过我们会发现,对于残余的块修改的时候,我们可能会违背这个块的并查集。所以对残余块修改时重构一下这个块的并查集。其它题解上面说只重构 \(x,y\),常数减半。 (但并没用什么用)

卡空间预定。

静态区间逆序对

题目链接 : \(\texttt{Yuno loves sqrt technology II}\)

要求求区间逆序对,并且离线。

首先开考虑 \(M \sqrt{N} \log N\) 的某做法,直接暴力莫队+权值线段树搞一搞即可。

然后我们考虑一下,这个莫队在算贡献的时候每走一步都要插入。其实我们可以把贡献表示一下,大概是这样子的 :

\(N(l,r)\)\(l,r\) 区间的逆序对。\(Max(l,r,x)\)\(l\)~\(r\) 中比 \(x\) 大的数字的个数。设 \(Min(l,r,x)\)\(l\)~\(r\) 中比 \(x\) 小的数字的个数。

\[N(l,r+1)=N(l,r)+Max(l,r,num_{r+1}) \]

\[N(l,r-1)=N(l,r)-Max(l,r-1,num_{r}) \]

\[N(l+1,r)=N(l,r)-Min(l+1,r,num_{l}) \]

\[N(l-1,r)=N(l,r)+Min(l,r,num_{l-1}) \]

我们考虑把这个东西差分一下,变成这个样子。由于左端点都是 \(1\),所以我就不写了。

\[N(l,r+1)=N(l,r)+Max(r,num_{r+1})-Max(l-1,num_{r+1}) \]

\[N(l,r-1)=N(l,r)-Max(r-1,num_{r})+Max(l-1,num_{r}) \]

\[N(l+1,r)=N(l,r)-Min(r,num_{l})+Min(l,num_{l}) \]

\[N(l-1,r)=N(l,r)+Min(r,num_{l-1})-Min(l-1,num_{l-1}) \]

然后我们用一个链式前向星存一下这个东西,用 \(Max(x,y)\) 或者 \(Min(x,y)\) 里面的 \(x\)\(y\)。由于莫队跑得最多的运算次数不会超过理论 \(M \sqrt{N}\),所以链表的空间是正确的。用链表的原因是为了动态内存。

然后我们考虑求 \(1\)\(x\) 的小于 \(y\) 的数字的个数,因为是离线,所以非常容易。我们考虑把权值分块,一开始指针 \(k=1\),然后插入 \(num_k\),并且维护一个前缀和,把后面所有的值弄上。这个用分块搞显然是 \(\sqrt{N}\) 的。 然后我们在 \(k\) 这个位置时候,看看 \(k\) 有没有指向一些 \(y\),然后把答案更新即可。时间复杂度是 \(O(1)\) 的,所有加在一起就是 \(O(M \sqrt{N})\) 的了。

然后我们再次跑一遍莫队,\(O(M \sqrt{N})\)

上述方法理论正确,但是常数很大。第一次莫队 \(M \sqrt{N}\),权值分块 \(N \sqrt{N}\),统计贡献 \(M \sqrt{N}\),第二次莫队 \(M \sqrt{N}\)。而权值分块和统计贡献 (因为分块的零散块只有一个,统计贡献是两个的,因为差分) 的常数为 \(2\),两次莫队常数为 \(3\)。所以可以达到 \(11 \times M \sqrt{N}\) 的神奇运算次数。

当然还有 \(Max\)\(Min\),所以这个东西可能会比 \(\log N\) 还要大..

代码留坑。


如果有比我给出的更好的算法 (指时间复杂度或者码量有质的飞跃),欢迎与我讨论。

posted @ 2019-09-14 09:07  _ARFA  阅读(183)  评论(0编辑  收藏  举报