「杂题乱写」Ynoi

「杂题乱写」Ynoi

点击查看目录

本文记阈值为 \(B\),块长为 \(L\),都比较接近 \(\sqrt{n}\) 但是需要根据具体情况平衡 . 如果压根不用平衡就直接写 $\sqrt{n} $ 了 .

其实直接写 \(\sqrt{n}\) 更省事吧,但是怎么看出来怎么平衡呢?

P7710 [Ynoi2077] stdmxeypz

考虑将问题转化到 dfn 序列上,子树修改转化为区间修改,考虑分块维护 . 散块是容易直接处理的,如何处理整块?

感觉很可以根号分治!设阈值为 \(B\),对于 \(x>B\),修改的点的数量不超过 \(\frac{n}{B}\) 但是并不方便直接暴力修改,考虑开一个标记数组 \(g_{i, j}\) 表示 \(i\) 的子树内与 \(i\) 距离为 \(j\) 的点的标记;对于 \(x\le B\),可以直接打上一个标记 \(f_{i, j}\) 表示该块内所有 \(dep_u\equiv j\bmod{i}\) 的点 \(u\) 的标记 . 查询时把所在块的标记跑一遍加上 .

注意到根号分治的阈值不必和块长一样,否则不会 TLE 会 MLE .

预处理 \(\mathcal{O}\left(n\right)\),修改 \(\mathcal{O}\left(L + \min\left(\dfrac{n}{L}, \dfrac{n}{B}\right)\right)\),查询 \(\mathcal{O}\left(B + \dfrac{n}{L}\right)\) .

Luogu Submission .

P5356 [Ynoi2017] 由乃打扑克

你发现 k-th 很难,但是 rank 好像更可做一点,于是二分,维护区间 rank .

不难想到对每个块排序,区间修改整块打标记,散块直接重构;区间 rank 整块 upper_bound,散块暴力跑一遍 .

但是感觉这个散块直接重构直接重构太耗时了,怎么办呢?发现一个块内没更改的部分是有序的,更改的部分也是有序的,那么考虑归并排序就把复杂度由 \(\mathcal{O}(L\log L)\) 降到了 \(\mathcal{O}(L)\) .

预处理 \(\mathcal{O}\left(n\log L\right)\),修改 \(\mathcal{O}\left(L + \dfrac{n}{L}\right)\),查询 \(\mathcal{O}\left(\log V\left(L + \dfrac{n}{L}\log L\right)\right)\)(内层 \(\log\) 来源于常数较小的 upper_bound) .

Luogu Submission .

P5309 [Ynoi2011] 初始化

和 stdmxeypz 有点像吧!为啥我不是先做的这个题?

依旧是根号分治,不过发现「保证 \(y\le x\)」,意味着修改是全局修改,不用对每个块都开一个标记数组 .

但是区间查询跑完所有标记是 \(\mathcal{O}(B^2)\) 的,非常慢,于是考虑维护标记的前后缀和,遍历标记时只用枚举 \(i\) 不必枚举 \(j\) .

卡常卡不动了,rk3,呜呜 .

可以直接把 \(B\) 设为 \(L\) .

预处理 \(\mathcal{O}\left(n\right)\),修改 \(\mathcal{O}\left(B\right)\),查询 \(\mathcal{O}\left(L\right)\) .

Luogu Submission .

P5046 [Ynoi2019 模拟赛] Yuno loves sqrt technology I

最卡常但是也是最喜欢的一个题!不过两者没有联系 .

首先从拆贡献入手:整块与整块之间,散块和整块之间,两个散块之间,散块和自己 .

思考过程太抽象了我还是不引导性讲解了 .

\(pre_{i}\)\(i\) 所在块的左端点到它的逆序对个数,\(suf_{i}\) 右端点,\(sum_{i, j}\) 表示块 \(i\sim j\) 的逆序对数量,\(f_{i, j}\) 表示 \(1\sim j\) 中的所有数与块 \(i\) 之间的逆序对数。(其实你也可以维护两个桶平替 BIT,但是常数没这个做法优秀,我是换成这个写法才过的,拜谢 @crimson000

预处理时,对每个块内排一遍序,因为归并排序可以求逆序对数,这样方便归并 . \(pre\)\(suf\) 是容易处理的,\(f\) 考虑在对整个数列排序后进行归并排序 . \(sum\) 可以直接拼起来,即 \(sum_{i, j - 1} + sum_{i + 1, j} - sum_{i + 1, j - 1}\) 加上 \(i\)\(j\) 之间的贡献,可以用 \(f\) 求 .

回到查询:

  • 整块与整块之间:\(sum\) 已经预处理出来了 .
  • 散块和整块之间:利用 \(f\) 数组差分求出 .
  • 两个散块之间:归并排序 .
  • 散块和自己:\(pre\)\(suf\),不过左右端点在同一个块时要归并一下去除多余贡献 .

虽然分讨但是没有太复杂的细节,每个地方都有一定趣味,更趣味的是题意非常典 .

预处理 \(\mathcal{O}\left(n\log L + \dfrac{n^2}{L} + \left(\dfrac{n}{L}\right)^2 \right)\),查询 \(\mathcal{O}\left(L + \dfrac{n}{L}\right)\) .

Luogu Submission .

P5047 [Ynoi2019 模拟赛] Yuno loves sqrt technology II

和 I 区别在于可以离线,于是很自然想到莫队 .

莫队在移动左右端点时,由于维护逆序对对个数,需要使用树状数组,因此总时间复杂度为 \(\mathcal{O}(n\sqrt{n}\log n)\) .

太慢了,考虑使用科技「莫队二次离线」来快速处理左右端点移动 .

具体的,假设当前区间是 \([l, r]\),将右端点移动到 \(r + 1\) 时,要求的 \(l\sim r\)\(r + 1\) 之间的逆序对数可以转化为差分 \(1\sim r\)\(r + 1\) 之间的逆序对数减去 \(1\sim l - 1\)\(r + 1\) 之间的逆序对数 . 前者直接树状数组扫一遍处理,后者离线将询问 \(r + 1\) 存在 \(l - 1\),从左往右扫的时候顺便处理 . 其他情况同理

不过发现左右端点移动 \(n\sqrt{n}\) 次,存下来的询问也有这么多,空间必定存不下,考虑把空间压成 \(\mathcal{O}(n)\) 级别的 . 每次移动的左右端点都是连续的区间,那么没必要存下所有点,直接存区间即可 .

但是如果我们依旧使用树状数组维护你会发现你好像折腾半天啥也没干,因为修改 \(\mathcal{O}(n)\) 次查询 \(\mathcal{O}(n\sqrt{n})\) 次套个 \(\log\) 复杂度又回去了,思考什么数据结构可以平衡一下,支持 \(\mathcal{O}(\sqrt{n})\) 单点修改,\(\mathcal{O}(1)\) 前缀查询 . 带个根号直接分块!把单点修改换成后缀修改,前缀查询就成了单点查询,于是就做完了 .

比预料之中好写不少 .

时间复杂度 \(\mathcal{O}\left(n\sqrt{n}\right)\),空间复杂度 \(\mathcal{O}\left(n\right)\) . 能做到这么快还是相当有趣的 .

好像有 \(\mathcal{O}(n^{1.41})\) 做法,有空看看 . 但是校内关网,我没梯子上不了论文网站 .

Luogu Submission .

P5048 [Ynoi2019 模拟赛] Yuno loves sqrt technology III

思维被绑住了!元素的信息其实并不一定需要和每个块绑在一块 .

首先预处理出来 \(cnt_{l, r}\) 表示第 \(l\sim r\) 的块中的众数 . 这个开一个临时桶很好处理 .

然后考虑如何快速处理散块贡献 . 开一个 std::vector 存每个值出现的所有位置,假设这个数组为 \(po\),对于左散块的每个 \(a_i\),假设其在 \(po\) 中的下标是 \(b_i\)check \(po_{a_i, b_i + ans}\) 是否仍在查询区间内,如果是则 \(ans\leftarrow ans + 1\) .

这个感觉,太巧妙了!

时间复杂度 \(\mathcal{O}(n\sqrt{n})\),空间复杂度 \(\mathcal{O}(n)\) .

Luogu Submission .

posted @ 2023-11-03 21:42  K8He  阅读(154)  评论(3编辑  收藏  举报