分块与莫队算法

1. 分块

分块的思想

分块是把一个长度为 \(N\) 的数列分为 \(\sqrt{N}\) 个块,每个块的长度为 \(\sqrt{N}\)。这样在区间操作的时候,对于每个涉及到的块,如果涉及到半个块,就直接操作;如果涉及到整个块,就整体操作。

下面通过例题来了解一下分块。

例1 洛谷-P3372

这道题可以用分块来做。

先把序列分为 \(\sqrt{N}\) 个块,用 \(sqr\) 表示块的数量,\(sum_i\) 表示第 \(i\) 个块中所有数的和,\(add_i\) 表示第 \(i\) 个块中所有标记的总和,\(len_i\) 表示第 \(i\) 个块的大小。

  • 区间修改

先判断 \(x,y\) 是否在同一个块,如果是就直接求和并输出。

否则再判断 \(x\) 是否为所在块的左端点,如果不是,就把这个块中 \(x\) 及其右边的数都直接加上 \(k\),并把 \(x\) 移动到下一个块。\(y\) 的移动同理。

接下来把 \(x,y\) 所在块中间的块的标记都加上 \(k\) 即可。

  • 区间查询

原理和区间修改相同。只不过在加整块的时候,需要加的是 \(add_i \times len_i\)

这样总时间复杂度为 \(\mathcal{O}(M \sqrt{N})\),可以通过此题。

代码

例2 洛谷-P3373

和 例1 类似,区间标记可以参考线段树

2. 基础莫队算法

基础莫队思想

基础莫队算法是一种离线算法。它适用于只查询不修改的区间问题。

基础莫队算法是基于分块的。它一般将所有操作读入后,按照块排序,然后处理并输出。

下面根据例题来理解一下基础莫队算法。

例1 洛谷-P3901

下面给出不同的解法。

  1. 扫描法

定义两个指针 \(L,R\),用 \(cnt_i\) 表示区间 \([L,R]\) 中每个数的出现次数。两个指针可以移动。当 \(L\) 左移或 \(R\) 右移时,区间中会加入一个数;当 \(L\) 右移或 \(R\) 左移时,区间中会减少一个数。设 \(ans\) 表示此时区间中不同数的个数。加入后,如果这个数的出现次数为 \(1\),则让 \(ans \leftarrow and+1\)。减少后,如果这个数的出现次数为 \(0\),则让 \(ans \leftarrow ans-1\)

这样可以对所有查询按左端点排序,每次通过移动指针来查询。这样的时间复杂度最差为 \(\mathcal{O}(NQ)\),不能通过。

  1. 基础莫队算法

基础莫队算法先将序列分块,然后将所有操作按左端点的块来排序,如果左端点的块相同,再按右端点排序。

这样看起来其实没啥优化,但是下图可以充分体现效果。用 \(L\) 表示横坐标,\(R\) 表示纵坐标,所有线段的长度即为两个指针移动的距离。

这样所有 \(\sqrt{N}\) 个块中,\(L\) 最多移动 \(Q \sqrt{N}\) 次,\(R\) 最多移动 \(Q \sqrt{N}\) 次。所以总时间复杂度为 \(\mathcal{O}(Q \sqrt{N})\),可以通过此题。

代码

posted @ 2024-02-08 20:06  lrx139  阅读(21)  评论(0编辑  收藏  举报