1 条题解

  • 0
    @ 2025-8-24 23:18:16

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar Gold14526
    nothing

    搬运于2025-08-24 23:18:16,当前版本为作者最后更新于2025-05-14 22:26:10,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    我操,单 log\log 彻底怒了。单 log\log 指出了最核心的矛盾点:如果 std 写的常数足够小,怎么可能 log2\log^2 轻轻松松直接通过?这确实是我的严重错误。我需要彻底承认 std 写的不够好,想办法把 DLESS Round 糊弄过去。


    以下设 hh 表示题面中的 nnn=2hn=2^h

    首先考虑一个性质:操作是有结合律的。

    例如,先执行 pi:=pix1p_i:=p_i\oplus x_1,再执行 pi:=pix2p_i:= p_i\oplus x_2,相当于执行一次 pi=pix1x2p_i=p_i\oplus x_1\oplus x_2,操作二也是类似。

    于是现在相当于我们对排列 pp,每次给出 x1,x2x_1,x_2,求排列 pp' 使得 pi=pix2x1p'_i=p_{i\oplus x_2}\oplus x_1 的逆序对数。


    不妨先考虑一下没有操作一怎么做。

    考虑我们平时有什么方式求逆序对,考虑通过分治的方式求逆序对。

    考虑当 nn22 的幂次时,每次分治时,我们都会将一个区间 [k2p,(k+1)2p)[k2^p,(k+1)2^p) 分成两个区间 $[2k2^{p-1},(2k+1)2^{p-1}),[(2k+1)2^{p-1},(2k+2)2^{p-1})$,然后再归并求出跨过中点的信息。

    我们把分治过程中 pp 相同的区间看作“一层”。

    考虑如果 x2x_2 的第 p1p-1 位为 11 会发生什么,此时左右两个区间会交换,在计算跨过中点的信息时,逆序对数应当是原来的正序对数,可以发现,这种交换在每一层是独立的,即只有 x2x_2 的第 pp 位为 11 时会交换第 pp 层向上合并的贡献,对其它位没有影响。

    于是,对于一层 pp,我求出这一层向上合并时,跨过上一层中点的正序对数和逆序对数,即可轻松解决询问,时间复杂度 O((n+q)logn)O((n+q)\log n)


    接下来考虑没有操作二怎么做。

    发现一个排列的逆序对数等于其逆排列的逆序对数,所以仿照没有操作一的做法即可完成。


    接下来,我们考虑两者都有的情况。

    我们考虑先按下标分治,接下来在每次分治时,我们将中点左侧的标记为 00,右侧的标记为 11,然后再按值域分治,求出每一层中跨过中点的标记正序对数和逆序对数即可得到一个 O(nlog2n+qlog2n)O(n\log^2n+q\log^2n) 的做法。

    实现时用线段树或 01-Trie 实现较为简便,实际上,这两种数据结构正是动态的分治。

    代码


    先来考虑如何优化查询部分。

    现在我们相当于有两张 logn×logn\log n\times\log n 的表格,分别表示 x1x_1x2x_2 的每一位两两之间的正序对和逆序对个数。

    但是我们可以做更多的预处理,比如说我们把它变成两个 n×lognn\times\log n 的表格,表示 x1x_1x2x_2 的每一位间的正逆序对个数,于是我们便可以支持单 log\log 查询。

    不过,我们可以把 nn 分成高 h2\frac{h}{2} 位和低 h2\frac{h}{2} 位,分成 44n×n\sqrt{n}\times\sqrt{n} 的块,这样便可以支持单次 O(1)O(1) 查询,那么查询的复杂度已经达到了最优。

    不过在本题的数据范围下单次查询 O(logn)O(\log n) 也足以通过。


    对于贡献的预处理部分,我们有至少两种 O(nlogn)O(n\log n) 做法:

    虚树做法

    建一棵原排列的 01-Trie SS,一棵逆排列的 01-Trie TT,每次对于 SS 上的一个节点求出该节点包含的数在 TT 上的虚树,在虚树上 dp 求出贡献。

    这种做法空间复杂度可以做到 O(n)O(n),时间复杂度 O(nlogn)O(n\log n),不难证明,这两者都已经到达了最优复杂度。

    可惜的是,这种做法常数要了命的大,即使在 n,qn,q10610^6 级别的范围下仍然无法快于双 log\log 做法,完全无法通过此题。

    代码

    Trie 树合并做法

    考虑分治,每次将分治左右两侧的 01-Trie 合并,并在合并过程中计算信息,当一个节点左右有来自不同 Trie 的子节点时,将它们子树中元素个数相乘更新贡献。

    这种做法空间复杂度为 O(nlogn)O(n\log n),时间复杂度 O(nlogn)O(n\log n),不过常数并不是很大,可以获得 8888 分。

    代码


    (这一部分做法感谢帮我们审核的管理 ppip)

    但是,真的只能获得 8888 分吗?

    常写树状数据结构的人肯定知道,我们可以进行垃圾回收。即,将删除的点重新在下次创建节点的时候利用。

    在本题中,只要加了垃圾回收,Trie 树合并做法空间就是 O(n)O(n) 的。

    此时,空间复杂度为同一时刻最多存在的节点数。

    设一棵 Trie 中存了 kk 个元素(并非节点),考虑把每一棵 Trie 分成上下两部分,上 logk\log k 层为上部分,剩下的为下部分。容易证明上下部分均只有 O(n)O(n) 个节点,所以总结点数为 O(n)O(n)

    于是,我们得到了常数并没有那么大的空间 O(n)O(n),时间 O(nlogn+q)O(n\log n+q) 做法。

    代码

    信息

    ID
    12299
    时间
    2000ms
    内存
    128~1024MiB
    难度
    7
    标签
    递交数
    0
    已通过
    0
    上传者