分块与块状链表
1. 算法分析
1.1 分块思想
分块思想就是将整体分为 n \sqrt n n个长度为 n \sqrt n n的区间,这样区间修改和查询的复杂度从 O ( n ) O(n) O(n)降为了 O ( n ) O(\sqrt n) O(n)
维护两个数组:add代表本段中的所有数都要加上add,sum代表本段的真实和是多少(算上了add)
修改一个区间时,分为完整段的区间修改以及前后两个不完整的段的区间修改:
对于完整段,add+=d,sum+=d*length
对于不完整的段:枚举所有元素,a+=d,sum+=d
查询时,对于完整段,累加sum,对于不完整的段,枚举元素
1.2 块状链表
如果操作需要进行插入、删除的操作的话,原来的分块就不适用了。因此引入了块状链表。块状链表就是将分块与链表加在一起,使用链表来维护不同分块之间的关系。
具体分为如下操作:
- 插入一段:(1) 分裂结点 O ( s q r t ( n ) ) O(sqrt(n)) O(sqrt(n)) (2) 在分裂点插入序列 O ( s q r t ( n ) ) O(sqrt(n)) O(sqrt(n))
- 删除一段:(1) 删除开头结点的后半部分 O ( s q r t ( n ) ) O(sqrt(n)) O(sqrt(n)) (2) 删除中间的完整结点 O ( s q r t ( n ) ) O(sqrt(n)) O(sqrt(n)) (3) 删除结尾结点的前半部分 O ( s q r t ( n ) ) O(sqrt(n)) O(sqrt(n))
- 合并:遍历整个链表,若下一个点可以合并到到当前结点,则合并。 O ( s q r t ( n ) ) O(sqrt(n)) O(sqrt(n))
其中,合并操作不需要每次都进行,可以设定一个阈值进行,比如:当分块的数目为 O ( s q r t ( n ) ) O(sqrt(n)) O(sqrt(n)) 时进行合并。
基本上块状链表的题目都可以使用splay来写,基本上可以互通
2. 板子
2.1 分块
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 100010, M = 350;
int n, m, len;
LL add[M], sum[M]; // add[i]表示第i块还需要加的值,sum[i]为第i块的权值和
int w[N];
int get(int i) {
return i / len; }
void change(int l, int r, int d) {
if (get(l) == get(r)) {
// 段内直接暴力
for (int i = l; i <= r; i++) w[i] += d, sum[get(i)] += d;
} else {
int i = l, j = r;
while (get(i) == get(l)) w[i] += d, sum[get(i)] += d, i++;
while (get(j) == get(r)) w[j] += d, sum[get(j)] += d, j--;
for (int k = get(i); k <= get(j); k++) sum[k] += len * d, add[k] += d; // 每一段的操作
}
}
LL query(int l, int r) {
LL res = 0;
if (get(l) == get(r)) {
// 段内直接暴力
for (int i = l; i <= r; i++) res += w[i] + add[get(i)];
} else {
int i = l, j = r;
while (get(i) == get(l)) res += w[i]