int sq = sqrt (n); for (int i = 1; i <= sq; i ++) { st[i] = n / sq * (n - 1) + 1; // st[i] 表示第 i 坨的第一个元素的下标 ed[i] = n / sq * i; // ed[i] 表示第 i 坨的最后一个元素的下标 }
但是数组的长度可能不是一个完全平方数,这样会漏一小坨,我们将其纳入最后一坨:
1
ed[sq] = n;
然后,为每一个元素确定它们属于的块。
1 2 3
for (int i = 1; i <= sq; i ++) // 遍历 sq 坨 for (int j = st[i]; j <= ed[i]; j ++) // 遍历刚刚记好的下标 bel[j] = i;
最后,如果必要,我们还要求一下每一坨的大小:
1 2
for (int i = 1; i <= sq; i ++) size[i] = ed[i] - st[i] + 1;
好了,到这里准备工作就好了,可以开始抛光了。
读入和预处理数据
输入以后,用 来保存每一个块的和。
1 2 3 4 5 6
for (int i = 1; i <= n; i ++) scanf ("%d", &a[i]); for (int i = 1; i <= sq; i ++) for (int j = st[i]; j <= ed[i]; j ++) sum[i] += a[i];
区间修改
首先是区间修改,分为两种策略,当左右端点在同一块内时,直接暴力修改原数组和 数组:
1 2 3 4 5 6
if (bel[x] == bel[y]) for (int i = x; i <= y; i ++) { a[i] += k; sum[bel[i]] += k; }
否则,先暴力修改左右两边零散区间:
1 2 3 4 5 6 7 8 9 10
for (int i = x; i <= ed[bel[x]]; i ++) // 从 x 到它属于的块的尾部 { a[i] += k; sum[bel[i]] += k; } for (int i = st[bel[y]]; i <= y; i ++) // 从 y 属于的块开始到 y { a[i] += k; sum[bel[i]] += k; }
然后在中间的整坨打上标记:
1 2
for (int i = bel[x] + 1; y < bel[y]; i ++) // 注意这里是按块遍历 mark[i] += k;
区间查询
同样地,如果左右两边在同一坨,直接暴力计算区间和。
1 2 3
if (bel[x] == bel[y]) for (int i = x; i <= y; i ++) s += a[i] + mark[bel[i]]; // 注意要加上标记
否则,暴力计算零碎块:
1 2 3 4
for (int i = x; i <= ed[bel[x]]; i ++) s += a[i] + mark[bel[i]]; for (int i = st[bel[y]]; i <= y; i ++) s += a[i] + mark[bel[i]];
再处理整块:
1 2
for (int i = bel[x] + 1; i < bel[y]; i ++) s += sum[i] + mark[i] * size[i]; // 标记也要乘上块长
int pool[N], tot; inlineintnew_node()// 分配节点 { return pool[tot --]; // 栈内元素减一(拿走) } inlinevoiddel(int x)// 回收节点 { pool[++ tot] = x; // 回收的这个节点成为栈顶元素 } inlinevoidinit()// 刚开始所有节点编号都存在垃圾桶里 { for (int i = 1; i < N; i ++) pool[++ tot] = N - i; // 注意这里栈顶元素是 1 }
链表节点
这个写个结构体就好,非常简单,记录每个节点所代表的数组长度和链表中下个节点:
1 2 3 4
structnode { int sz, nxt; // 该节点对应数组长度和下一个节点 int s[N]; // 数组 } a[N];
查询位置
我们一次一次用大位置减去块代表数组的长度,当不够减了或者已经是链表结尾就结束:
1 2 3 4 5 6 7 8 9 10 11
inlineintpos(int &p)// 返回原数组中下标为 p 的元素所在的块的编号,并将传入的引用(原数组下标)改为块内下标 { // 这一段有点像珂朵莉树里暴力查询的代码,如果不能理解建议溜去那边看看 int x = 0; // 这里的 x 是指第 x 块,即节点编号 while (x != -1 && a[x].sz < p) // 当这个光标能够减去的长度小于这个块长时,就说明属于该块,要不就是最后一块 { p -= a[x].sz; // 用 p 减去这个节点中数组长度 x = a[x].nxt; // 直接传送到下一个节点去 } return x; // 返回属于块的编号 }
暴力插入
在 后面插入一个 节点,那么 的指针(即后面的节点)会继承 的指针,而 的指针就指向 :
1 2 3 4 5 6 7 8 9 10 11
inlinevoidadd(int x, int y, int len, int l, int r)// 暴力插点 { if (y != -1) // 当它不是重新开的那个节点 { a[y].sz = len; a[y].nxt = a[x].nxt; // y 的指针就是 x 的指针,即指向原来 x 的后面的节点 for (int i = 1, j = l; i <= len, j <= r; i ++, j ++) a[y].num[i] = s[j]; } a[x].nxt = y; // x 的指针重新调到 y 身上 }
节点合并
同化,蚕食,生存的方式。
合并 和 节点:
1 2 3 4 5 6 7 8
inlinevoidmerge(int x, int y)// 节点合并 { for (int i = a[x].sz + 1, j = 1; i <= a[x].sz + a[y].sz, j <= a[y].sz; i ++) a[x].num[i] = a[y].num[j]; a[x].sz += a[y].sz; // 复制信息 a[x].nxt = a[y].nxt; // x 继承了 y 的指针 del (y); // 删除 y }
inlinevoidremove(int x, int len)// 删除一段区间 { int now = pos (x); split (now, x); // 在左端点分割 int nxt = a[now].nxt; while (nxt != -1 && len >= a[nxt].sz) // 尽量删一整块 { len -= a[nxt].sz; nxt = a[nxt].nxt; } if (nxt != -1) // 特判节点为空 { spilt (nxt, len); // 就在右端点分割 nxt = a[nxt].nxt; // 定位删除后的右边部分 } for (int i = a[now].nxt; i != nxt; i = a[now].nxt) { a[now].nxt = a[i].nxt; // 继承指针 del (i); } while (nxt != -1 && a[now].sz + a[nxt].sz < sq) // 暴力合并剩下的两个小块块 { merge (now, nxt); // 直接暴力合并 nxt = a[nxt].nxt; } }
查询
从第 个元素,找一条 的条条,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
voidget(int x, int len)// 从第 x 个元素开始(大体上看的 x ) { int now = pos (x), tot = min (a[now].sz - x, len), cur = 0; // 先处理左边不完整的部分 for (int i = a[now].sz - tot + 1; i <= a[now].sz; i ++) // 左边块处理 ans[++ cur] = a[now].num[i]; int nxt = a[now].nxt; // 转移指针 while (nxt != -1 && len >= a[nxt].sz + tot) // 整块整块的弄起来 { for (int i = 1; i <= a[nxt].sz; i ++) ans[++ cur] = a[nxt].num[i]; tot += a[nxt].sz; nxt = a[nxt].nxt; } if (nxt != -1 && tot < len) // 右侧如果还有剩余就再搞 for (int i = 1; i <= len - tot; i ++) ans[++ cur] = a[nxt].num[i]; for (int i = 1; i <= cur; i ++) printf ("%d ", ans[i]); }