CF1423G Growing flowers

发布时间 2023-11-28 11:21:08作者: 蒟蒻·廖子阳

我永远喜欢数据结构。

洛谷 CF

  • 给出长度为 \(n\) 的序列 \(a_1\sim a_n\),有 \(q\) 次操作:

    • \(1\texttt{ }l\texttt{ }r\texttt{ }c\),对于 \(i\in [l,r]\),执行 \(a_i\leftarrow c\)

    • \(2\texttt{ }x\),查询所有长度为 \(x\) 的子区间中数的种数和。即:

      \[\sum\limits_{i=1}^{n-x+1}\sum_{j\in V} [\exists \,k\in [i,i+x-1],a_k=j] \]

      其中 \(V\) 为值域。

  • \(n,q\le 10^5\)\(|V|\le 10^9\)

  • \(4.00\,\text{s}\,/\,250.00\,\text{MB}\)

下文称数为颜色。

tags:

  • \(\text{data structures}\)

  • \(\color{maroon}*3500\)

3k5 纯 DS 太酸爽了。

来一篇没那么感性的题解。

一句话题解:正难则反,考虑贡献。说了和说了一样。

先离散化,考虑改写一下式子:

\[\begin{aligned}\sum\limits_{i=1}^{n-x+1}\sum_{j\in V} [\exists \,k\in [i,i+x-1],a_k=j]&= \sum\limits_{j\in V} \sum\limits_{i=1}^{n-x+1}[\exists \,k\in [i,i+x-1],a_k=j]\\&= \sum\limits_{j\in V} \left(n-x+1-\sum\limits_{i=1}^{n-x+1}[\nexists \,k\in [i,i+x-1],a_k=j]\right)\\&= |V|\cdot (n-x+1)-\sum\limits_{j\in V} \sum\limits_{i=1}^{n-x+1}[\nexists \,k\in [i,i+x-1],a_k=j]\end{aligned} \]

维护一个数组 \(b_x\) 表示 \(\sum\limits_{j\in V}\sum\limits_{i=1}^{n-x+1}[\nexists \,k\in [i,i+x-1],a_k=j]\),则答案为 \(|V|\cdot (n-x+1)-b_x\)

考虑 \(b_x\) 的含义是什么,就是对于每种颜色,求出原数组中有多少个长度为 \(x\) 的子区间不包含它,然后将这些个数加起来。

我们再考虑对于当前序列构造一个 \(|V|\times n\) 的带权网格 \(A\)。其中:

\[A_{i,j}=\begin{cases}1,a_j=i\\0,\text{otherwise}\end{cases} \]

那么 \(b_x\) 就等于这个网格中横向的、长度为 \(\boldsymbol{x}\) 的全 \(\boldsymbol 0\) 连续段个数(下文简称为空连续段)。因为艾弗森括号内的式子等价于网格中这个连续段的值全部为 \(0\)

注意到当网格是由当前序列构造而成时,\(b_x\) 才有如上含义。但是对于任意一个网格,不管它是否由当前序列构造而成,⌈长度为 \(x\) 的空连续段个数⌋ 这个量都是有意义的。因此再引入一个数组 \(d_x\) 表示当前网格中长度为 \(\boldsymbol x\) 的空连续段个数

看到 \(1\) 操作是区间推平的形式,考虑使用珂朵莉树维护。我们发现区间推平相当于对于 \(c\) 颜色删去了一些空连续段,又对于区间内的其它颜色加入了一些空连续段。

此处 ⌈删去一个空连续段⌋ 的定义是将该段内的所有 \(0\) 变成 \(1\)

我们只需要维护每次增 / 删空连续段后的 \(A\) 网格 / \(d\) 数组,就可以得到增 / 删完这么多次,即推平后的序列对应的 \(A\) 网格 / \(d\) 数组。此时 \(d\) 数组和 \(b\) 数组相同,就可以拿它去计算答案了。

而且初始序列对应的 \(A\) 网格 / \(d\) 数组也可以通过 ⌈一个初始全 \(1\) 的网格,先把每一行全部变成 \(0\),再对于 \(i\in [1,n]\),执行 \(A_{a_i,i}\leftarrow 1\)⌋ 这样的方式构造出来。

考虑一次增 / 删操作对于 \(d\) 数组而言到底是怎样变化。

  • 设增加一个长度为 \(L\) 的空连续段,则对于 \(x>l\)\(d_x\) 无变化。对于 \(x\in[1,l]\)\(d_x\) 增加了 \(L-x+1\)。因为多出了这么多可供选择的左端点。

  • 设删除一个长度为 \(L\) 的空连续段,则对于 \(x>l\)\(d_x\) 无变化。对于 \(x\in[1,l]\)\(d_x\) 减少了 \(L-x+1\)。因为少了这么多可供选择的左端点。

我们发现对于 \(d\) 数组的修改是一个 ⌈区间加等差数列⌋ 的形式。因此实际上要支持的操作是 ⌈区间加等差数列⌋ 和 ⌈单点查询⌋。考虑维护 \(d\) 数组的差分数组,即可转变成区间加定值和区间求和。线段树维护即可。

考虑一次推平操作具体是怎样增 / 删空连续段。首先考虑空连续段怎么变。

  • 对于 \(c\) 颜色,先将其在 \([l,r]\) 内的非空连续段删去,再增加一个 \([l,r]\) 的非空连续段。

  • 对于其它颜色,将其再 \([l,r]\) 内的非空连续段删去。

考虑这样引起了怎样的空连续段的变化。

  • 对于某种颜色删去一个非空连续段 \([l,r]\) 时,考虑找到它的前驱 \([l_1,r_1]\) 和后继 \([l_2,r_2]\)。那么相当于先删去 \((r_1,l)\)\((r,l_2)\) 两个空连续段,再增加 \((r_1,l_2)\) 这个空连续段。

  • 对于某种颜色加入一个非空连续段 \([l,r]\) 时,同样考虑找到它的前驱、后继,那么相当于先删去 \((r_1,l_2)\) 这个空连续段,再加入 \((r_1,l)\)\((r,l_2)\) 这两个空连续段。

这样我们就知道如何维护区间推平后的 \(A\) 网格以及 \(d\) 数组的。

查询平凡,对于差分数组单点查询即可。然后带入式子得到答案。

时间复杂度可以这么分析:

  • 初始有 \(\mathcal{O}(n)\) 个连续段,每次操作加入 \(\mathcal{O}(1)\) 个连续段,一共有 \(\mathcal{O}(n+m)\) 个连续段。

  • 一个连续段只有在插入 / 删除时才会进行操作,共 \(\mathcal{O}(1)\) 次。一次操作有 \(\mathcal{O}(1)\)set 上二分以及线段树区间修改,时间复杂度为 \(\mathcal{O}(\log n)\)

  • 所以修改操作的时间复杂度为 \(\mathcal{O}((n+m)\log n)\),查询操作的时间复杂度为 \(\mathcal{O}(m\log n)\),因此总时间复杂度为 \(\mathcal{O}((n+m)\log n)\),常数巨大。可能是我 #define int long long 的原因。

空间复杂度显然为 \(\mathcal{O}(n+m)\)。至此问题得到解决。

提交记录(\(\color{limegreen} \bf Accepted\)\(\boldsymbol{2.23 \,\text{s}\,/\, 32.94\,\text{MB}}\),含代码)