题解:P8419 [THUPC 2022 决赛] riapq

· · 题解

前言

发现我不会写分块分块分块,严肃学习了分块分块分块。
严肃参考 IOI 金牌大神的题解。

思路

题意很明确了。
不弱于区间逆序对,于是我们分块分块分块!!!

块长取根号是最优复杂度,那我直接用根号来讲吧。
设块长为 \sqrt{n} 对序列分块。接下来考虑每一次修改造成的几种贡献:

  1. 左右散块内部
  2. 左散块对右散块
  3. 整块对左散块
  4. 单个整块内部
  5. 整块对整块
  6. 左散块对整块

左右散块内部:

好做。预处理对于第 i 块中的第 j 个数,这一块的前 k 个数有多少比它小,复杂度 O(n\sqrt{n})。每一次修改直接在 b 数组上面加。

左散块对右散块:

好做。预先块内排序。每一次修改用指针扫一遍,直接在 b 数组上面加。给个代码。

//c[i].x 是值,c[i].y 是原序列位置 
int LL=belong[l],RR=belong[r];
int p=L[LL],sum=0;
for(int i=L[RR];i<=R[RR];i++){
    while(p<=R[LL]&&c[p].x<c[i].x)sum+=(c[p].y>=l),p++;
    if(c[i].y<=r)b[c[i].y]+=sum;
}

整块对右散块:

好做。预处理出前 i 块中有多少个数小于 j。直接拿个 O(\sqrt{n}) 单点修 O(1) 查的值域分块平衡一下复杂度。复杂度 O(n\sqrt{n})。每一次修改直接在 b 上加。

单个整块内部:

好做。修改时直接对每块打标记。询问时,用标记乘上块内询问点前面的数的贡献即可。可以直接用上做左右散块内部中预处理的数组。

整块对整块:

好做。维护对于每一个整块,其他块对它的贡献次数。用差分平衡一下复杂度,修改时枚举每一块 O(1) 修改这一块的差分数组,在查询时可以 O(\sqrt{n}) 复原出原数组然后统计。

左散块对整块:

不好做!

构建一个平面,x 坐标为块编号,y 坐标为 a 值。直接维护修改不好做,于是维护的是差分数组。
比如说对于 a 这个数,我们要在 lr 的块上更新贡献,那么只需要修改 x 坐标为 lr+1 上的点,在 y 坐标上加点就可以了。每一次询问就做一遍二维数点。

然后我们怎么维护它呢?我们对它进行分块分块分块。

在 $y$ 坐标,我们先以 $\sqrt{n}$ 分块,维护块的前缀和。这样每一次修改,我们就先把它给差分回去,进行 $\sqrt{n}$ 次单点修改后再前缀和。 但是询问时 $y$ 坐标上散块的复杂度有一点炸了,是 $O(\sqrt{n})$,而我们希望得到的是 $O(\sqrt[4]{n})$ 的复杂度。这咋办?对每个散块再分块!散块内部的长度是 $O(\sqrt{n})$,那么我们可以做到 $O(1)$ 单点修改与 $O(\sqrt[4]{n})$ 区间和。 这样这部分就做好了!复杂度是单次修改查询 $O(\sqrt{n})$。 代码是好写的。具体的建议参考 [IOI 金牌大神的题解](https://www.luogu.com.cn/article/ta3eb2zy)的代码,码风很好。 --- 做完了。复杂度是 $O(n\sqrt{n})$,但由于高维数组带来的友善常数,完全跑不过 $O(n\sqrt{n}\log n)$,甚至于本题也会被卡常。可以用 C++17 来交,应该就能过了。 如果你想去过 [Ynoi 的双倍经验](https://www.luogu.com.cn/problem/P9987),那最好不要用这种方法,不然卡常卡死你。