P5386 [Cnoi2019] 数字游戏

我永远喜欢数据结构。

题目传送门

  • 给出长度为 n排列 a1anm 次询问有多少对 [l,r] 满足 LlrRi[L,R],ai[X,Y]

  • n,m2×105

  • 3 s7 s / 125 MB

下文中默认 O(n)=O(m),分块以 O(n) 为块长。

考虑对 [X,Y] 这个限制莫队。设当前维护的值域区间是 [x,y],令 wi=[xaiy]。对于一个询问我们要求当 x,y 分别扫到 X,Y 时,w 数组的区间 [L,R] 内有多少全部为 1 的子区间。

由于给出的是一个排列,x,y 移动一步时只会带来 w 数组一个位置的改变。相当于要支持单点 01(或 10),区间查询 [L,R] 内有多少全部为 1 的子区间(简称为答案)。

考虑线段树,一个节点维护一下几个信息:

  • len:代表这个节点对应的区间长度。

  • llen:代表这个节点对应的区间中,以左端点开始的最长连续 1 段长度。

  • rlen:代表这个节点对应的区间中,以右端点结束的最长连续 1 段长度。

  • sum:代表这个节点对应的区间的答案。

合并两个节点 u,v 的信息时,都考虑该信息是否跨过中点。具体地,设 为合并操作:

  • lenuv=lenu+lenv

  • llenuv={lenu+llenv,lenu=llenullenu,otherwise

  • rlenuv={lenv+rlenu,lenv=rlenvrlenv,otherwise

  • sumuv=sumu+sumv+rlenullenv

容易支持单点修改。这样做时间复杂度为 O(nnlogn),空间复杂度为 O(n)。不够优美。

原因是修改数量为 O(nn),而询问数量仅有 O(n)。考虑换成 O(1) 修改 O(n) 查询的分块。

记:

  • bli,bri 为第 i 块的左右端点。

  • belii 位置所在块。

  • Ai 为第 i 块的信息。

  • posi 的意义如下:若 i这个块中一个极长 1 连续段的端点,则 posi 的值为它所在极长 1 连续段的另一个端点;否则 posi=0

考虑 wi01 怎么修改。考虑修改后极长 1 连续段的情况怎么变,显然它只可能接上修改前 i1 所在极长 1 连续段和 i+1 所在极长 1 连续段。以此来处理 pos 数组的变化。注意不存在这两个位置或这两个位置与 i 不在同一块中的情况,具体可以看代码,个人认为讨论了所有可能的情况,虽然有一些繁杂。

处理好修改后 i 所在极长 1 连续段后,新增的答案就是这个段中包含 i 的子区间数量,是容易计算的。

至于 llenrlen 的变化,容易通过修改后的 pos 数组求出。

但是发现 wi10 的情况不好处理,于是考虑回滚莫队,每次撤销最新的一次 01 回退上一个版本。用栈按时间顺序存储修改的量即可。

查询可以考虑从左往右遍历每个查询区间中的块(包括散块),散块暴力计算信息,整块就用维护好的 Ai,然后像上面那样合并。

处理询问时,对于左右端点在莫队中分出的块相同时,对于 [X,Y] 这个值域,让指针 jX 扫到 Y 暴力更新 [X,j] 的信息。然后查询、撤销。单次时间复杂度为 O(n)

剩下的询问离线跑回滚莫队。每次做一段左端点在莫队中分出的块相同的询问。假设这一块的右端点为 R0,我们只考虑撤销 [X,R0] 的操作,保留 (R0,Y] 的操作。这样对于排序后的每一个询问 i,可以先从上一个询问的 (R0,Yi1] 扩展到这个询问的 (R0,Yi],再添加 [X,R0] 的操作,再查询,再撤销这些操作回到 (R0,Yi] 的版本,再继续做下一个询问。

做完一段左端点所在块相同的询问后,暴力清空分块。由于只会有 O(n) 种左端点所在块,即这么多次清空,所以清空的总时间复杂度是 O(nn)

修改的和查询的总复杂度都是 O(nn)。因此,回滚莫队的做法时间复杂度为 O(nn),空间复杂度为 O(n)。可以接受。

AC Link

AC Code

posted @   lzyqwq  阅读(38)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示