<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Sqrt Decomposition |</title><link>https://sichen-wang.github.io/tags/sqrt-decomposition/</link><atom:link href="https://sichen-wang.github.io/tags/sqrt-decomposition/index.xml" rel="self" type="application/rss+xml"/><description>Sqrt Decomposition</description><generator>HugoBlox Kit (https://hugoblox.com)</generator><language>en-us</language><lastBuildDate>Sat, 24 Jun 2023 11:00:56 +0000</lastBuildDate><image><url>https://sichen-wang.github.io/media/icon_hu_9dfcb674aa885f38.png</url><title>Sqrt Decomposition</title><link>https://sichen-wang.github.io/tags/sqrt-decomposition/</link></image><item><title>数列分块</title><link>https://sichen-wang.github.io/blog/%E6%95%B0%E5%88%97%E5%88%86%E5%9D%97/</link><pubDate>Sat, 24 Jun 2023 11:00:56 +0000</pubDate><guid>https://sichen-wang.github.io/blog/%E6%95%B0%E5%88%97%E5%88%86%E5%9D%97/</guid><description>&lt;h2 id="基本思想"&gt;基本思想&lt;/h2&gt;
&lt;p&gt;考虑一些在序列上的区间操作，如区间加和区间求和。&lt;/p&gt;
&lt;p&gt;直接暴力做单次操作复杂度是 \(\Theta(n)\)，总时间复杂度 \(\Theta(qn)\).&lt;/p&gt;
&lt;p&gt;如果将序列分成 \(\sqrt{n}\) 块，每块长度为 \(\sqrt{n}\)，并且对每个块维护里面所有元素的和，即可在 \(\Theta(\sqrt{n})\) 的复杂度内进行单次操作。&lt;/p&gt;
&lt;p&gt;具体实现如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;令块的数量为 \(num=\sqrt{n}\)，单个块的长度为 \(siz=\lceil\frac{n}{num}\rceil\).&lt;/li&gt;
&lt;li&gt;记 \(bel_{i}\) 表示 \(i\) 号节点属于第几个块。&lt;/li&gt;
&lt;li&gt;可以记录一些题目需要用到的信息，如 \(sum_{i}\) 表示第 \(i\) 个块内所有元素的和。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="数列分块入门-9-题"&gt;数列分块入门 9 题&lt;/h2&gt;
&lt;h3 id="t1-区间修改-单点查询"&gt;T1 区间修改, 单点查询&lt;/h3&gt;
&lt;p&gt;对每个块维护整块加的值，查询的时候加上即可。&lt;/p&gt;
&lt;p&gt;时间复杂度 \(\Theta(n\sqrt{n})\)。&lt;/p&gt;
&lt;h3 id="t2-区间修改-查区间内小于某个数的个数"&gt;T2 区间修改, 查区间内小于某个数的个数&lt;/h3&gt;
&lt;p&gt;对每个块内的元素排序，区间修改时整块大小顺序不变，散块暴力排序，查询时二分即可。&lt;/p&gt;
&lt;p&gt;时间复杂度 \(\Theta(n\sqrt{n}\log n)\)。&lt;/p&gt;
&lt;p&gt;排序使用归并可以将复杂度优化到 \(\Theta(n\sqrt{n\log n})\)。&lt;/p&gt;
&lt;h3 id="t3-区间修改-区间查前驱"&gt;T3 区间修改, 区间查前驱&lt;/h3&gt;
&lt;p&gt;同样是排序后在块内二分，只不过是把求比他小的数的数量改成求比它小的数中最大的那个。&lt;/p&gt;
&lt;p&gt;时间复杂度 \(\Theta(n\sqrt{n}\log n)\)。&lt;/p&gt;
&lt;p&gt;排序使用归并可以将复杂度优化到 \(\Theta(n\sqrt{n\log n})\)。&lt;/p&gt;
&lt;h3 id="t4-区间修改-区间求和"&gt;T4 区间修改, 区间求和&lt;/h3&gt;
&lt;p&gt;维护每个块的和以及加的数，用类似 \(\rm T1\) 的方法维护。&lt;/p&gt;
&lt;p&gt;时间复杂度 \(\Theta(n\sqrt{n})\)。&lt;/p&gt;
&lt;h3 id="t5-区间开方-区间求和"&gt;T5 区间开方, 区间求和&lt;/h3&gt;
&lt;p&gt;我们发现开方操作是没办法全局维护的，于是考虑寻找开方操作的性质。&lt;/p&gt;
&lt;p&gt;观察到每个数被开方很少的次数以后就会变成 \(1\)（或者它本身就是 \(0\)，但这部分显然没有贡献，可以直接删去），于是对每个块记录一下里面 \(1\) 的个数，如果全都是 \(1\)，就不对它进行开方，否暴力开方即可。&lt;/p&gt;
&lt;p&gt;求区间和还是维护一下每个块的和。&lt;/p&gt;
&lt;p&gt;时间复杂度 \(\Theta(n\sqrt{n})\)。&lt;/p&gt;
&lt;h3 id="t6-单点插入-单点查询"&gt;T6 单点插入, 单点查询&lt;/h3&gt;
&lt;p&gt;我们发现暴力做的时候需要将所有数向后移一位，单次时间复杂度 \(\Theta(n)\)。&lt;/p&gt;
&lt;p&gt;于是考虑分块，给每个块预留 \(\sqrt{n}\) 的剩余空间用来后移，记一下当前块的长度即可。查询的时候每访问一个块就把查询编号减去当前块的长度。不难发现这样做单次操作时间复杂度 \(\Theta(\sqrt{n}).\)&lt;/p&gt;
&lt;p&gt;但是我们发现一个问题，如果插入全在一个块内的话复杂度很快就会变成 \(\Theta(n)\)，于是考虑每进行插入操作 \(\sqrt{n}\) 次就重构分块即可。&lt;/p&gt;
&lt;p&gt;时间复杂度 \(\Theta(n\sqrt{n})\)。&lt;/p&gt;
&lt;h3 id="t7-区间修改包含加和乘-单点查询"&gt;T7 区间修改(包含加和乘), 单点查询&lt;/h3&gt;
&lt;p&gt;考虑对每个块记 \(\rm add\) 和 \(\rm mul\)，表示块内元素的真实值为 \(a\times \rm mul+add\)，区间加的时候，给 \(\rm add\) 加上 \(k\) 即可，区间乘的时候，不仅需要给 \(\rm mul\) 乘上 \(k\)，还需要给 \(\rm add\) 乘上 \(k.\)&lt;/p&gt;
&lt;p&gt;时间复杂度 \(\Theta(n\sqrt{n})\)。&lt;/p&gt;
&lt;h3 id="t8-区间查询数的个数并全部修改成这个数"&gt;T8 区间查询数的个数并全部修改成这个数&lt;/h3&gt;
&lt;p&gt;我们发现如果询问区间小的话查询和修改都很容易，如果询问区间大的话会把很长一段都变成相同的数。如果记录一个块是否都是同一个数的话就很容易维护了。&lt;/p&gt;
&lt;p&gt;不难发现这样做复杂度是 \(\Theta(n\sqrt{n})\) 的。&lt;/p&gt;
&lt;h3 id="t9-询问区间最小众数"&gt;T9 询问区间最小众数&lt;/h3&gt;
&lt;p&gt;记 \(s_{i,j}\) 表示前 \(i\) 个块中 \(j\) 出现的次数，\(p_{i,j}\) 表示第 \(i\) 个块到第 \(j\) 个块的众数。&lt;/p&gt;
&lt;p&gt;我们发现，对于一次询问，答案一定是整块的众数或散块的众数，这个只需要对比一下即可。&lt;/p&gt;
&lt;p&gt;时间复杂度 \(\Theta(n\sqrt{n})\)。&lt;/p&gt;
&lt;h2 id="经典例题"&gt;经典例题&lt;/h2&gt;
&lt;h3 id="tjoi2009-开关"&gt;[TJOI2009] 开关&lt;/h3&gt;
&lt;p&gt;记 \(c_{i}\) 表示第 \(i\) 盏灯是否开着，\(sum_{i}\) 表示第 \(i\) 个块中有多少盏灯开着，\(d_{i}\) 表示第 \(i\) 个块的状态是否整体改变。&lt;/p&gt;
&lt;p&gt;对于区间取反操作，零散操作直接修改 \(c_{i}\)，并根据 \(c_{i}\) 和 \(d_{i}\) 决定 \(sum_{i}+1\) 或 \(-1\). 整块操作不用改变 \(c\)，直接令 \(sum_{i}=R_{i}-L_{i}+1-sum_{i}\)，并且将 \(d_{i}\) 取反。&lt;/p&gt;
&lt;p&gt;对于区间求和操作，零散点求和直接加上 \(c_{i}\oplus d_{i}\)，整块求和则加上 \(sum_{i}\)。&lt;/p&gt;
&lt;p&gt;单次操作复杂度 \(\Theta(\sqrt{n})\)，总时间复杂度 \(\Theta(q\sqrt{n})\)。&lt;/p&gt;
&lt;h3 id="lg3396-哈希冲突"&gt;[LG3396] 哈希冲突&lt;/h3&gt;
&lt;p&gt;转化题意，设计一种数据结构，支持：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;单点修改&lt;/li&gt;
&lt;li&gt;求从某一点 \(pos(pos\leqslant d)\) 开始，以步长 \(d\) 遍历数列，求所经过的所有数之和。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;考虑根号分治。&lt;/p&gt;
&lt;p&gt;对于 \(d\geqslant \sqrt{n}\) 的询问，直接暴力查询。&lt;/p&gt;
&lt;p&gt;对于 \(d \leqslant \sqrt{n}\) 的询问，记 \(sum_{i,j}\) 表示从 \(i(i\leqslant \sqrt{n})\) 开始，步长为 \(j(j\leqslant \sqrt{n})\) 的答案。预处理的时候直接枚举计算即可，时间复杂度 \(\Theta(n\sqrt{n})\)。&lt;/p&gt;
&lt;p&gt;对于修改操作 \(a[pos]\leftarrow x\)，先直接在原数列上进行修改，再考虑其对 \(sum\) 数组造成了什么影响。易知，对于步长为 \(d\) 的预处理数组，只会对 \(sum_{pos\%d,d}\) 造成影响，于是对每个 \(d(d\leqslant \sqrt{n})\) 修改一下即可，单次修改时间复杂度 \(\Theta(\sqrt{n})\)。&lt;/p&gt;
&lt;p&gt;总时间复杂度 \(\Theta[(n+q)\sqrt{n}]\)。&lt;/p&gt;
&lt;h3 id="lg2801-教主的魔法"&gt;[LG2801] 教主的魔法&lt;/h3&gt;
&lt;p&gt;考虑分块。&lt;/p&gt;
&lt;p&gt;记原数列为 \(A\)，将 \(A\) 复制一份记为 \(B\)，将 \(B\) 分块，块内排序。&lt;/p&gt;
&lt;p&gt;对于修改操作，整块直接打标记，散点修改完后再块内排序，这样完成操作后仍能保持分块数列的性质，单次操作复杂度 \(\Theta(\sqrt{n}\log\sqrt{n})\)。&lt;/p&gt;
&lt;p&gt;对于查询操作，二分查找第 \(i\) 个块内有多少个元素不小于 \(k-add_{i}\)，单次操作复杂度 \(\Theta(\sqrt{n}\log \sqrt{n})\)。&lt;/p&gt;
&lt;p&gt;总时间复杂度 \(\Theta[(n+q\sqrt{n})\log n]\)。&lt;/p&gt;
&lt;p&gt;考虑再对复杂度做一些优化，令块的大小为 \(s\)，则修改操作若采用归并排序，单次操作复杂度为 \(\Theta(s+\frac{n}{s})\)，同时单次查询复杂度为 \(\Theta(s+\frac{n}{s}\log s)\)。当询问次数为 \(q\) 时，总复杂度为 \(\Theta[q(s+\frac{n}{s}\log n)]\geqslant \Theta(q\sqrt{n\log n})\)，取最小值当且仅当 \(s=\sqrt{n\log n}\)。&lt;/p&gt;
&lt;h3 id="国家集训队-排队"&gt;[国家集训队] 排队&lt;/h3&gt;
&lt;p&gt;分块 \(+\) 树状数组。&lt;/p&gt;
&lt;p&gt;考虑交换两个数 \(a_{u}\) 和 \(a_{v}\) 的贡献：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;显然 \(u\) 左边的数和 \(v\) 右边的数没有贡献。&lt;/li&gt;
&lt;li&gt;对于 \(a_{u}\) 来说，贡献为 \(u\) 到 \(v\) 之间比它大的数的数量减去比它小的数的数量。&lt;/li&gt;
&lt;li&gt;对于 \(a_{v}\) 来说，贡献为 \(u\) 到 \(v\) 之间比它小的数的数量减去比它大的数的数量。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;至于如何计算 \(u\) 到 \(v\) 之间比某个数大或小的值的数量，分块后散块直接暴力，整块建两颗树状数组即可。&lt;/p&gt;
&lt;p&gt;令块大小为 \(siz\)，则时间复杂度为 \(\Theta[nlogn+q(siz+\frac{n}{siz}logn)]\geqslant nlogn+q\sqrt{nlogn}\)，取最小值当且仅当 \(siz=\sqrt{nlogn}\)，此时块的数量 \(num=\sqrt{\frac{n}{logn}}\)。&lt;/p&gt;
&lt;h3 id="lg4168-violet-蒲公英"&gt;[LG4168] Violet 蒲公英&lt;/h3&gt;
&lt;p&gt;考虑分块，记 \(s_{i,j}\) 表示前 \(i\) 个块中颜色为 \(j\) 的数量，\(p_{i,j}\) 表示第 \(i\) 个块到第 \(j\) 个块的众数。&lt;/p&gt;
&lt;p&gt;对于一次询问，先求出整块中的众数、众数的出现次数和所有在零散块中出现了的颜色的数量。显然，如果零散块中出现的颜色没有一个比众数出现的次数多，那么要求的众数就是整块的众数，否则是出现次数最多的颜色。&lt;/p&gt;
&lt;p&gt;时间复杂度 \(\Theta(n\sqrt{m})\)。此时块大小 \(B=\frac{n}{\sqrt{m}}\)。&lt;/p&gt;</description></item></channel></rss>