专栏文章

莫队与分块

个人记录参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@miql5f46
此快照首次捕获于
2025/12/04 06:35
3 个月前
此快照最后确认于
2025/12/04 06:35
3 个月前
查看原文

P2709 小B的询问


  • 给定一个长度为 nn 的整数序列 aa,值域 [1,m][1 , m]。有 qq 个询问,每次给定一个区间 [l,r][l , r],求 i=lrci2\displaystyle \sum_{i = l}^{r} c^{2}_icic_i 表示数字 ii[l,r][l , r] 中的出现次数。
  • 1n1051 \leq n \leq 10^51m1051\leq m \leq 10^51k1051\leq k \leq 10^5
  • 莫队模板题。
  • 将原序列分成若干块,每块 块长BB,把所有询问 离线 下来,以左端点 ll 所在块的编号 为第一关键字,右端点 rr 的大小为第二关键字排序。
  • 考虑维护指针 iijj 分别表示当前区间的左右端点,用 llrr 分别表示当前询问区间的左右端点
    • 若有 i>li > l 则需要让 ii 向左移动:i -- ,后删除 ii cnt[a[i]] -- ,在删除的同时,更新一下当前的答案 ans
    • i<li<lj<rj < rj>rj > r 的情况同理。
  • 一个小结论:若需要添加 (add),则一定是先移动指针,后添加;若删除 (del),则一定是先删除,后移动指针。
  • 一个小细节:初始时,把 i=1i = 1j=0j = 0,表示当前区间为空,还没有添加任何元素。
  • 复杂度分析:
  • 对于 ii 的移动:
    • 若两次询问的左端点 ll 在一个块内,则两个询问之间最多需要 ii 移动 BB 次,qq 个询问最多 q×Bq\times B
    • 若两次询问左端点 ll 所在的块变化,考虑分析最坏情况,画个图即可发现 ii 移动距离不会超过 2×n2\times n。因此复杂度忽略不计。
  • 对于 jj 移动:
    • jj 移动时,两次询问的左端点 ll 始终在一个块内,由于右端点 rr 单调,每个块jj 移动次数 总共 不超过 nnnB\dfrac{n}{B} 个块,则 jj 移动次数不超过 n×nB=n2Bn\times \dfrac{n}{B} = \dfrac{n^2}{B}
    • jj 移动时,两次询问的左端点 ll 所在块发生了变化。nB\dfrac{n}{B} 个块,则最多发生 nB\dfrac{n}{B} 次这样的变化,每次 jj 的变化最多是 nn,则 jj 的移动最多是 n×nB=n2Bn \times \dfrac{n}{B} = \dfrac{n^2}{B}
  • 由于 BB 太大时,ii 移动的复杂度过大;BB 太小时,jj 移动复杂度过大。考虑找到一个平衡点,令 n2B=q×B\dfrac{n^2}{B} = q \times B,解得 B=nqB = \dfrac{n}{\sqrt{q}}。因此最终复杂度为 O(n×q)\mathcal{O}(n\times \sqrt{q})
  • trick:当块编号是奇数时,按照 rr 递增排序;编号是偶数时,按照 rr 递减排序。

P1494 小 Z 的袜子


  • 给定 nn 个袜子,每一个袜子都有一个颜色 aia_i
  • 给定 qq 组询问,每次给定一个区间 [l,r][l , r],问从区间 [l,r][l , r] 随机抽出两只颜色相同袜子的概率。
  • 1n1051 \leq n \leq 10^5, 1q1051\leq q \leq 10^5, 1ain1\leq a_i \leq n
  • 概率为 i=1n(ci2)(rl+12)\frac{\displaystyle \sum_{i = 1}^{n} \binom{c_i}{2}}{\displaystyle \binom{r - l + 1}{2}}cic_i 表示颜色 ii 在区间 [l,r][l , r] 出现的次数。
  • 用莫队维护 cic_i,以及 i=1n(ci2)\displaystyle \sum_{i = 1}^{n} \binom{c_i}{2} 即可。

P4462 [CQOI2018] 异或序列


  • 给定一个长度为 nn 的序列 aa 和一个参数 mm
  • qq 组询问,每次给定两个参数 l,rl , r,问有多少个点对 (i,j)(i , j) 满足 lijrl \leq i \leq j \leq r,并且 aiai+1...aj=ma_i \oplus a_{i + 1} \oplus ... \oplus a_j = m
  • 1n1051 \leq n \leq 10^51q1051\leq q \leq 10^51ain1\leq a_i \leq n
  • 首先 aiaja_i \sim a_j 的异或和可以用前缀异或和 SiSj1S_i \oplus S_{j - 1} 来表示
  • 莫队,考虑指针 iijj 移动时顺便更新答案。设 cnt[x][0]\text{cnt}[x][0] 表示 iji \sim jSk1=xS_{k - 1} = x 的个数,cnt[x][1]\text{cnt}[x][1] 表示 iji \sim jSk=xS_k = x 的个数。
  • 注意,移动指针时,cnt[x][0]\text{cnt}[x][0]cnt[x][1]\text{cnt}[x][1] 都需要更新。并且 要注意更新 cnt\text{cnt} 数组和更新答案的先后顺序!

loj 6277


  • 给定一个长度为 nn 的序列 aa
  • nn 个操作,每个操作都有三个参数 opt\text{opt}llrrcc
  • 1n1051\leq n \leq 10^5,保证答案在 int 范围内。
    • opt = 0\text{opt = 0},把区间 [l,r][l , r] 的每一个数都加上 cc
    • opt = 1\text{opt = 1},查询 ara_r 的值
  • 对数列分块。build 时算出 p[i]bl[i]br[i],分别表示 aia_i 所在的块的编号,aia_i 所在块的左端点的编号,以及右端点的编号。
  • 修改时,先检查是否 llrr 在同一个块(即 p[l] = p[r])。若在一个块,则暴力即可;若不在,给中间的整块打上加法标记,散块暴力即可。单次修改复杂度 nB+B\dfrac{n}{B} + B
  • 查询时,直接让 ara_r 和其所在块的加法标记相加即可。
  • nB=B\dfrac{n}{B} = B,得 B=nB = \sqrt{n},因此总复杂度 O(mn)\mathcal{O}(m \sqrt{n})mm 表示询问次数

loj 6278


  • 给定一个长度为 nn 的序列 aa
  • nn 个操作,每个操作都有四个参数 opt\text{opt}llrrxx
    • opt=0\text{opt} = 0,把区间 [l,r][l , r] 的数都加上 xx
    • opt=1\text{opt} = 1,查询区间 [l,r][l , r] 小于 xx 的数的个数。
  • 1n1051\leq n \leq 10^5,保证答案在 int 范围内。
  • 对数列分块,设块长为 BB。对每一个块维护一个递增的数列,以及区间的加法标记。
  • 查询时,对于整块二分即可;对于散块直接暴力枚举即可。注意,由于区间有加法标记,所以要找的是这个块的递增数列第一个小于 xtag[k]x - \text{tag}[k] 的位置。tag[k]\text{tag}[k] 表示 xx 所在块的加法标记。单次查询复杂度 nB×logB+B\frac{n}{B}\times \log B + B
  • 修改时,对于整块,打上加法标记即可,并且维护的递增数列的单调性不会被破坏。对于散块,暴力给每个 a[i]a[i] 加上 xx。由于破坏了散块递增数列的单调性,所以需要给这个块重新排序。单次修改复杂度 nB+B×logB\frac{n}{B} + B\times \log B
  • 我们希望查询和修改复杂度能够相等,令 nB×logB+B=nB+B×logB\frac{n}{B}\times \log B + B = \frac{n}{B} + B\times \log B,解得 B=nB = \sqrt{n},因此总复杂度 O(n×n×logn)\mathcal{O}(n \times \sqrt{n} \times \log n)

loj 6279


  • 给定一个长度为 nn 的序列 aa
  • nn 个操作,每个操作都有四个参数 opt\text{opt}llrrxx
    • opt=0\text{opt} = 0,把区间 [l,r][l , r] 的数都加上 xx
    • opt=1\text{opt} = 1,查询区间 [l,r][l , r] 小于 xx 的数的最大值。
  • 1n1051\leq n \leq 10^5,保证答案在 int 范围内。
  • 思路和上一题类似。

loj 6280


  • 给定一个长度为 nn 的序列 aa
  • nn 个操作,每个操作都有四个参数 opt\text{opt}llrrxx
    • opt=0\text{opt} = 0,把区间 [l,r][l , r] 的数都加上 xx
    • opt=1\text{opt} = 1,输出 i=lrai\displaystyle \sum_{i = l}^{r} a_i(x+1)(x + 1) 取模后的答案。
  • 1n1051\leq n \leq 10^5不保证答案在 int 范围内
  • 对数列分块,设块长为 BB。对于每一个块,维护区间加法标记 tag[k]\text{tag}[k] 以及区间和 sum[k]tag[k]\text{tag}[k] 表示这个块内的每个元素需要都加上 tag[k]\text{tag}[k],但现在还没有加。
  • 修改时,对于整块,更新 tag[k] 以及 sum[k] 即可。对于散块,暴力更新 a[i]sum[k] 即可,tag[k] 无需更新。单次修改复杂度 B+nBB + \dfrac{n}{B}
  • 查询时,对于整块,直接查询 sum[k] 即可。对于散块,暴力累加 a[i] + tag[k] 即可。单次查询 B+nBB + \dfrac{n}{B}
  • B=nB = \sqrt{n},则单次查询和修改复杂度均为 n\sqrt{n},因此总复杂度 O(mn)\mathcal{O}(m \sqrt{n})mm 表示操作次数。

loj 6281


  • 给定一个长度为 nn 的序列 aa
  • nn 个操作,每个操作都有四个参数 opt\text{opt}llrrxx
    • opt=0\text{opt} = 0,对区间 [l,r][l , r] 中的每一个数 aia_i 执行 aiaia_i \leftarrow \lfloor \sqrt{a_i} \rfloor
    • opt=1\text{opt} = 1,输出 i=lrai\displaystyle \sum_{i = l}^{r} a_i
  • 1n1051\leq n \leq 10^5,保证答案在 int 范围内。
  • 将数列分块,设块长为 BB。维护块内区间最大值 mx[k],以及块内区间和 sum[k]
  • 修改时:
    • 对于散块,暴力给 aia_i 开根号,更新块内最大值即可,复杂度为 BB
    • 对于整块,若块内区间最大值 mx[k] >1>1,则暴力给整个块的 aia_i 开根号。枚举整块复杂度为 nB\dfrac{n}{B}。对于每一个整块,最多开 55 次根号,因此给整块暴力开根号的 总复杂度nB×B×5=5×n\dfrac{n}{B} \times B \times 5 = 5\times n,可忽略不计。
    • 因此,可以认为单次修改复杂度为 B+nBB + \dfrac{n}{B}
  • 查询时,对于散块,暴力加 aia_i 即可,复杂度 BB。对于整块,直接加区间和 sum[k] 即可,复杂度 nB\dfrac{n}{B}。因此单次查询复杂度为 B+nBB + \dfrac{n}{B}
  • B=nB = \sqrt{n},则总复杂度为 O(mn)\mathcal{O}(m\sqrt{n})mm 为操作次数

loj 6282


  • 给定一个长度为 nn 的数列。
  • nn 个操作,每次给定四个参数 opt\text{opt}llrrcc
    • opt=0\text{opt} = 0,表示在第 ll 个数字前插入数字 rr
    • opt=1\text{opt} = 1,表示查询 ara_r 的值。
  • 根号重构
  • 初始时,对数列分块,块长为 n\sqrt{n}
  • 对于每一个块维护一个数组 b[k][x],表示编号为 kk 的块的数组的第 xx 个元素,插入时,从小到大枚举块的编号,找到对应的块,插入即可。单次插入复杂度为 块的个数 ++ 对应的块的大小
  • 由于频繁的插入会导致某一个块的大小过大,最坏有可能是 nn 级别的。因此每隔 n\sqrt{n} 次插入,我们就重构一次,这样可以保证所有块的大小不超过 2n2\sqrt{n}
  • 插入复杂度为 n\sqrt{n},重构的总复杂度为 qn×n=qn\dfrac{q}{\sqrt{n}} \times n= q\sqrt{n}
  • 查询时,依旧从小到大枚举块的编号,找到对应的块即可。查询复杂度为 n\sqrt{n}
  • 总复杂度 O(q×n)\mathcal{O}(q \times \sqrt{n})

牛客多校 Distance


  • 给定一个 n×m×hn \times m \times h 的空间。
  • qq 次操作,每次给定四个参数 opt\text{opt}xxyyzz
  • opt=1\text{opt} = 1,表示给点 (x,y,z)(x , y , z) 打上标记
  • opt=2\text{opt} = 2,表示查询 (x,y,z)(x , y , z) 到已经标记的点的曼哈顿距离的最小值。
  • 1n×m×h1051\leq n \times m \times h \leq 10^5, 1q1051\leq q \leq 10^5
  • 根号重构。令 N=n×m×hN = n \times m \times h
  • 考虑暴力怎么做:把被标记的点塞到一个集合(vector) 里面,每次查询的时候暴力枚举集合内的点。
  • 但是我们不希望每次都枚举集合内所有点。
  • 注意到不管关键点有多少个,把每一个关键点作为起点,跑一遍多源 bfs,都可以在 O(N)\mathcal{O}(N) 的时间复杂度内算出任意一个点到关键点的最小值。
  • 考虑根号重构。每新标记 BB 个点后就重新跑一遍多源 bfs,发现跑完 bfs 之后需要枚举的点不超过 BB 个。因此复杂度为 O(N×qB+q×B)\mathcal{O} (N\times \dfrac{q}{B} + q\times B),令 B=NB = \sqrt{N},总复杂度为 O(q×N)\mathcal{O}(q\times \sqrt{N})

loj 6283


  • 给定一个长度为 nn 的序列 aa
  • nn 个操作,每次操作给定四个参数 opt\text{opt}llrrcc
    • opt=0\text{opt} = 0,表示给 alara_l \sim a_r 加上 cc
    • opt=1\text{opt} = 1,表示给 alara_l \sim a_r 乘上 cc
    • opt=2\text{opt} = 2,表示输出 ara_r1000710007 取模后的结果
  • 1n1051\leq n \leq 10^5,答案保证无需开 long long
  • 对数列分块,块长为 n\sqrt{n}。对每一个块维护乘法标记 tag[k][0]\text{tag}[k][0] 和加法标记 tag[k][1]\text{tag}[k][1]
  • 修改时,若是整块:
    • 如果是区间加法,则直接让加法标记加上 cc 即可
    • 如果是区间乘法,则需要让乘法标记、加法标记都乘上 cc
  • 若是散块:一定要先下放标记(让散块内的每一个数都乘上 tag[k][0]\text{tag}[k][0] 然后加上 tag[k][1]\text{tag}[k][1]),然后再暴力修改。

loj 6285


  • 给定一个长度为 nn 的序列 aa
  • nn 组询问,每次给定两个参数 llrr,表示询问区间最小众数。
  • 1n1051\leq n \leq 10^5231ai2311-2^{31} \leq a_i \leq 2^{31} - 1
  • 对序列分块,块长为 n\sqrt{n}。预处理 ans[i][j] 表示编号在 iji \sim j 的块的最小众数,以及 cnt[i][j] 表示前 ii 个整块内数字 jj 出现的次数。预处理时,第一层枚举最左边块的编号 ii,第二层枚举最右边的编号 jj,第三层枚举第 jj 块的所有数字,加入桶中,统计答案。复杂度 O(nn)\mathcal{O} (n\sqrt{n})
  • 查询时,先让答案(最小众数)继承整块的答案 ans[i][j],然后枚举散块内的元素,加入桶中,某一个数 xx 此时此刻出现次数为在整块出现次数 cnt[r][x] - cnt[l - 1][x]lr 分别是最左边整块和最右边整块的编号),加上在散块中出现次数 bucket[x],尝试用二者之和更新答案即可。

P2617 Dynamic Rankings


  • 给定一个长度为 nn 的序列 aa
  • qq 次操作,操作分两种:
  • 1.1. 查询区间 [l,r][l , r]kk 小值。
  • 2.2. 单点修改,把 ax=ya_x = y
  • 1n,m1051\leq n,m \leq 10^50ai,y1090 \leq a_i,y\leq 10^9
  • 算是动态二维数点吧,lrl \sim r 算第一维,第 kk 小算第二维。
  • 查询时,跳块即可。跳块复杂度 O(n)\mathcal{O}(\sqrt{n}),为了不让复杂度退化成 O(n)\mathcal{O}(n),考虑根号平衡,设 wi,jw_{i , j} 表示值域在第 ii 块,并且序列下标在 1j1 \sim j 块的个数,vi,jv_{i , j} 表示值域为 ii,并且序列下标在 1j1\sim j 块的个数。这样就算出了 ((值域上的整块,序列上的整块 )) 的个数,以及 (( 值域上的散块,序列上的整块 )) 的个数。
  • 对于 (值域上整块,序列上散块),可以先把序列上的散块的数 aia_i 对应的值域上块的编号 aiB\lfloor \dfrac{a_i}{B} \rfloor 装到桶里。对于(值域上散块,序列上散块)的个数,可以先把序列上的散块的数 aia_i 加入桶里。

牛客多校第 6 场 E Array


  • 给定一个长度为 nn 的序列,初始所有元素为 00
  • 给定一个长度为 nn 的序列 pp,满足 1pin1 \leq p_i \leq n,有 qq 次操作吗,操作分两种:
  • 1.1. 给定参数 llrrxx,给 alara_l \sim a_r 加上 xx
  • 2.2. 给定参数 llrr,问 i=lrapi\displaystyle \sum_{i = l}^{r} a_{p_i}
  • 1n1051\leq n \leq 10^51x1081\leq x \leq 10^8
  • 把问题放到二维平面上来看,(i,pi)(i , p_i) 表示第 iipip_i 行,查询相当于查 lrl \sim r 所有列的点权之和。修改相当于给 lrl \sim r 行所有点的点权加上 xx
  • 考虑把列分块,每 BB 个分成一组。查询的时候可以查 整组之和 以及单独的列(即 apia_{p_i}
  • 修改时可以直接考虑对 查询的整组 的贡献,对于每一组,把组内元素按照 pip_i 排序,通过二分,可得有多少个组内元素的列 pip_ilrl \sim r 之内(记作 cnt\text{cnt}),则本次修改对该组的贡献为 cntx\text{cnt} \cdot x
  • 修改时,对于零散的行,以及整块的行,考虑其对零散的列的贡献。其实,就是等价于:区间 +x+x,每次查 BB 次某个具体的位置 apia_{p_i} 的值。把行分块,根号平衡即可。
  • B=n×lognB = \sqrt{n \times \log n},得到 O(q×n×logn\mathcal{O}(q \times \sqrt{n \times \log n} 的复杂度。代码
  • 另一种做法是把所有点按照 pip_i 排序,然后每 BB 个分一组,这样,最多只有 BB 个零散的点需要暴力修改。查询时,考虑对列分块,块长为 BB
  • 首先考虑 修改查询时整块 的贡献。设 cnt(i,j)\text{cnt}(i , j) 表示第 ii 组整体 +1+1 后对第 jj 个整块列的贡献,hih_i 表示第 ii 组整体被加的标记。则查询时需要 枚举所有整组,需要 O(1)\mathcal{O}(1) 知道一个整组对一些整块的贡献,把 cnt(i,j)\text{cnt}(i , j) 的第二维前缀和即可。散点 +x+x 对整块的贡献设一个数组即可。
  • 修改查询时散列 的贡献,可以和上个做法一样,用一个分块,也可以分别考虑修改时零散的点对查询时零散的列、以及整块的贡献。
  • 注意 vector 的一些细节:对 ord 排序时,不能是单纯地 sort(ord.begin() , ord.end()),因为定义固定长度的 vector 时,初始里面的元素都是 00

评论

0 条评论,欢迎与作者交流。

正在加载评论...