分块

T1 [LOJ]分块入门5

给出一个长为\(n\)的数列\(a_i\),以及\(n\)个操作,操作涉及区间开方,区间求和。

\(n\le 5\times 10^4,0\le a_i\le 2^{31}-1\)

Solution

思考一下发现区间开方是比较棘手的地方,因为它不能直接维护啊。

那我们可以试图找一些有关开方的性质。(这里的开方带向下取整)

显然就是一个特别大的数经过很少次数的开方,就会变成\(0\)或者\(1\)。尝试一下发现在范围内,一个数最多被开方\(5\)次就会变成\(0\)\(1\)

也就是说,一个数经过最\(5\)次开方后,后面的操作就对它没有影响了。所以我们就可以标记一下每一个块内是否都\(\le 1\),修改的时候跳过不需要修改的块。

那时间复杂度呢?我们将零散位置与整块分开考虑:

  • 每一次遍历的零散位置最多有\(\sqrt{n}\)个,所以这一部分的复杂度就是\(O(n\sqrt{n})\)
  • 每一个块最多被修改\(5\)次,每次修改就是将块内所有数暴力开方,所以这一部分的复杂度是\(O(5\times块的数量\times块长)=O(5\times\sqrt{n}\times\sqrt{n})=O(5n)\)次。

所以总的时间复杂度为\(O(n\sqrt{n}+5n)=O(n\sqrt{n})\)


T2 [LOJ]分块入门6

给出一个长为\(n\)的数列\(a_i\),以及\(n\)个操作,操作涉及单点插入,单点询问。

\(n\le 10^5\)

Solution

这个东西好像是叫“\(\text{vector}\)实现的块状链表”来着?

我们可以用若干个\(\text{vector}\)来维护每一个块,插入的时候可以直接暴力\(O(\sqrt{n})\)插入,当块长大于\(2\sqrt{n}\)的时候就将整个数列重构,当然也可以定期重构,或者把\(2\)改为别的常数。

重构就是暴力把每个数拿出来,然后再每\(\sqrt{n}\)个数组成一块放回去,复杂度\(O(n)\)

由于最多有\(\sqrt{n}\)次重构,\(n\)次插入,所以总的时间复杂度为\(O(n\sqrt{n}+n\sqrt{n})=O(n\sqrt{n})\)


T3 [Ynoi]由乃打扑克

给你一个长为\(n\)的序列\(a_i\),需要支持\(m\)次操作,操作有两种:

  1. 查询区间\([l,r]\)的第\(k\)小值。
  2. 区间\([l,r]\)加上\(k\)

\(n,m\le 10^5\)\(-2\times 10^4\le\)每次加上的数和原序列的数\(\le 2\times 10^4\)

Solution(伪)

考虑分块,区间加很简单,但是直接维护第\(k\)小貌似不好做。

所以对于每次询问,我们可以二分第\(k\)小值,则需要支持判断某一个数的排名是否为\(k\)

将序列分为\(B\)块,每次判断计算区间内\(\le mid\)的值的个数,对于整块在块内二分,零散位置暴力统计即可。

时间复杂度\(O(m(\frac{n}{B}logBlogV+B))\),当\(B=\sqrt{n}logn\)时最优,为\(O(m\sqrt{n}logV+m\sqrt{n}logn)=O(m\sqrt{n}logn)\)

理论上会被卡,但是加上这些剪枝可以通过:

  • 判断块端点来尽量回避二分
  • 二分\(k\)小值的时候,将端点设置为询问区间最值

但这仍然是一个伪算法,正解还不会,溜了。


T4 [CodeChef]Chef and Churu

给定长度为\(n\)的数列\(a_i\),给定\(m\)个函数\(f\),其中\(f_i\)\(a_i\)中第\(l_i\)到第\(r_i\)个数的和。

需要支持\(q\)次操作,操作类型分为两种:

  1. \(a_x\)修改为\(y\)

  2. 求第\(x\)到第\(y\)函数的和

\(n,m,q\le 10^5,a_i\le 10^9\)

Solution

注意到函数不变,所以对函数分块,维护块内和。

维护\(show[i,j]\)表示第\(i\)个块中,下标\(j\)的出现数(如果一个函数的区间包含\(j\),则\(j\)出现一次)。

对于一次修改,第\(i\)个块的和\(sum[i]\)将会被更新为\(sum[i]+(y-a_x)\cdot show[i,x]\)

对于询问,整块\(O(1)\)算,零散点需要动态维护\(a_i\)的区间和。

那时间复杂度呢?

设块长为\(B\)。每次修改遍历所有块,即\(\frac{n}{B}\)个块,每个块\(O(1)\)修改,复杂度为\(O(\frac{n}{B})\)

对于查询,遍历\(\frac{n}{B}\)个整块与\(B\)个零散点。如果使用树状数组维护\(a\)的区间和,则复杂度为\(O(\frac{n}{B}+Blogn)\)

这种做法的总时间复杂度为\(O(q(\frac{n}{B}+Blogn))\)\(B\)\(\sqrt{n}\)时为\(O(q\sqrt{n}logn)\),可以通过。

Better Solution

那个多出来的\(log\)其实是多在了维护\(a_i\)的区间和上。

每次修改是对\(a_x\)的单点修改,查询是区间和。一共\(q\)次修改,\(qB\)次询问。

我们可以想到根号平衡,用一个\(O(\sqrt{n})\)单点修改,\(O(1)\)区间查询的分块来解决即可。

  • 具体方法:对\(a_i\)分块,维护块内前缀和、块外前缀和。修改的时候\(O(\sqrt{n})\)修改这两个东西,查询的时候用两个前缀和拼起来即可。

这种做法的总复杂度是\(O(q(\frac{n}{B}+\sqrt{n}+B))\)\(B\)\(\sqrt{n}\)时最优,为\(O(q\sqrt{n})\)

posted @ 2021-12-08 19:42  hzy1  阅读(104)  评论(0编辑  收藏  举报