块状链表详解+例题

引入

块状链表,顾名思义,就是把分块和链表结合起来的神奇数据结构。

  • 分块区间操作复杂度优秀,但是不能支持 插入/删除 操作。

  • 链表单点插入删除复杂度优秀,但是不能支持大规模的区间操作。

但是两者相结合,就会变得非常无敌。

块状链表思想

块状链表的实现原理根本上就是保证每个块的大小稳定在 \(\sqrt{n}\) 级别,从而保证块数也为根号级别。但是插入数据之后,可能会出现某个快过大的情况。当块长 \(> 2 \sqrt{n}\) 时,可以将块分裂成两个,保证每块大小稳定在 \([\sqrt{n}, 2\sqrt{n}]\)。这样分裂的复杂度是单根号的。

这样保证了块数为根号级别,定位一个数的位置也可以在 \(O(\sqrt n)\) 复杂度内解决。只要能定位数所在的位置,就可以用 \(O(1)\) 复杂度完成插入和删除,其他操作都跟分块复杂度相同。

块状链表如何维护数据

Luogu 普通平衡树模板 做例题。

要支持在 \(O(\sqrt n)\) 复杂度内维护一个有序序列。

首先需要建一个双链表(某些题可以单链表),作为外层数据结构。

然后再每个链表里面维护一个小链表(可以用 vector 实现),来维护当前块。另外,由于后续操作写成 (p -> vector).lower_bound((p -> vector).begin(), (p -> vector).end(), x) 这样的形式不是很美观,也可以在块链类内部提前封装类似函数。

  • 块状链表定义形式

struct node { // 构建块状链表类
    node *next, *pre;
    vector<int> v;
    node() { next = pre = NULL; }
    void insert(int x) { v.emplace_back(x); }
    int vsize() { return v.size(); }
    int back() { return v[v.size() - 1]; }
    int front() { return v[0]; }
    vector<int>::iterator vbegin() { return v.begin(); }
    vector<int>::iterator vend() { return v.end(); }
    vector<int>::iterator lower(int x) { return lower_bound(v.begin(), v.end(), x); }
    vector<int>::iterator upper(int x) { return upper_bound(v.begin(), v.end(), x); }
}*head, pool[1010], *idx;

我拿指针实现的。当然用 vector 更简便,也更慢。

  • 块的分裂

暴力赋值即可。复杂度为 \(O(\sqrt n)\)

void split(node *p) { // 将大块 p 分裂成两个小块,S为每块的长度阈值
    node *q = new(idx ++ )node();
    q -> next = p -> next; if (q -> next) q -> next -> pre = q;
    p -> next = q, q -> pre = p;
    rop(i, S, p -> vsize()) q -> insert((p -> v)[i]);
    (p -> v).erase(p -> vbegin() + S, p -> vend());
}

对于普通平衡树这题来说,我们维护的序列是有序的。因此可以直接按照权值来查找。定位当前数在那个块也非常容易。

复杂度 \(O(\sqrt n)\)

  • 定位

node *find(int x) { // 找到 x 所在块
    for (node *p = head; p; p = p -> next)
        if (p -> back() >= x) return p;
}

剩下的操作就很套路了。就不再赘述了。

复杂度当然是 \(O(n \sqrt n)\),跑的好像比 \(\texttt{splay}\) 还要快一点。大概因为常数比较小

代码示例

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>

#define rep(i, a, b) for (int i = (a); i <= (b); i ++ )
#define rop(i, a, b) for (int i = (a); i < (b); i ++ )
#define dep(i, a, b) for (int i = (a); i >= (b); i -- )
#define dop(i, a, b) for (int i = (a); i > (b); i -- )

using namespace std;

const int INF = 2e9;
int n, S;

struct node { // 构建块状链表类
    node *next, *pre;
    vector<int> v;
    node() { next = pre = NULL; }
    void insert(int x) { v.emplace_back(x); }
    int vsize() { return v.size(); }
    int back() { return v[v.size() - 1]; }
    int front() { return v[0]; }
    vector<int>::iterator vbegin() { return v.begin(); }
    vector<int>::iterator vend() { return v.end(); }
    vector<int>::iterator lower(int x) { return lower_bound(v.begin(), v.end(), x); }
    vector<int>::iterator upper(int x) { return upper_bound(v.begin(), v.end(), x); }
}*head, pool[1010], *idx;

node *find(int x) { // 找到 x 所在块
    for (node *p = head; p; p = p -> next)
        if (p -> back() >= x) return p;
}
void split(node *p) { // 将大块分裂成两个小块
    node *q = new(idx ++ )node();
    q -> next = p -> next; if (q -> next) q -> next -> pre = q;
    p -> next = q, q -> pre = p;
    rop(i, S, p -> vsize()) q -> insert((p -> v)[i]);
    (p -> v).erase(p -> vbegin() + S, p -> vend());
}
void insert(int x) { // 插入操作
    node *p = find(x); (p -> v).emplace(p -> lower(x), x);
    if (p -> vsize() >= 2 * S) split(p);
}
void remove(int x) { // 移除数 x 
    node *p = find(x); // 找到 x 的位置
    (p -> v).erase(p -> lower(x));
    if (p -> vsize() == 0) { // 如果当前块空了
        if (p == head) { // 如果是头结点,更新头结点
            head = p -> next, head -> pre = NULL;
            return;
        }
        // 更新与下一个节点的关系
        p -> pre -> next = p -> next;
        p -> next -> pre = p -> pre;
        p -> next = p -> pre = NULL;
    }
}
int get_rank(int x) { // 根据权值找排名
    int cnt = 0;
    for (node *now = head; now; now = now -> next) {
        if (now -> back() >= x) {
            cnt += (now -> lower(x)) - (now -> vbegin()); return cnt + 1;
        }
        else cnt += now -> vsize();
    }
    return cnt + 1;
}
int get_kth(int k) { // 根据排名找权值
    int cnt = 0;
    for (node *now = head; now; now = now -> next) {
        if (cnt + now -> vsize() < k) {
            cnt += now -> vsize(); continue;
        }
        return (now -> v)[k - cnt - 1];
    }
}
int get_pre(int x) { // 找前驱
    node *p = find(x);
    if (p -> front() >= x) return p -> pre -> back();
    return *((p -> lower(x)) - 1);
}
int get_next(int x) { // 找后继
    node *p = find(x);
    if (p -> back() == x) return *(p -> next -> upper(x));
    return *(p -> upper(x));
}

int main() {
    idx = pool;
    scanf("%d", &n);
    S = (int)sqrt(n); // 块长
	head = new(idx ++ )node();
    (head -> v).emplace_back(INF);

	while (n -- ) {
		int op, x;
		scanf("%d%d", &op, &x);
		if (op == 1) insert(x);
		if (op == 2) remove(x);
		if (op == 3) printf("%d\n", get_rank(x));
		if (op == 4) printf("%d\n", get_kth(x));
		if (op == 5) printf("%d\n", get_pre(x));
		if (op == 6) printf("%d\n", get_next(x));
	}

	return 0;
}

例题

Loj 数列分块入门6

这道题可以分块 + 重构,也可以用块状链表。

按照下标顺序插入(注意,和普通平衡树的插入方式不同,这里以下标为关键字),单点查询时直接定位块就可以了。

然鹅跑的不是很快。代码贴一下。

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>
#define vit vector<int>::iterator
#define rep(i, a, b) for (int i = (a); i <= (b); i ++ )
#define rop(i, a, b) for (int i = (a); i < (b); i ++ )
#define dep(i, a, b) for (int i = (a); i >= (b); i -- )
#define dop(i, a, b) for (int i = (a); i > (b); i -- )

using namespace std;

using LL = long long;
using PII = pair<int, int>;
using PLL = pair<LL, LL>;

int n, S;
const int INF = 2e9;

struct node {
	node *pre, *next; vector<int> v;
	node() { pre = next = NULL; v.clear(); }
	vit vbegin() { return v.begin(); }
	vit vend() { return v.end(); }
	vit lower(int x) { return lower_bound(v.begin(), v.end(), x); }
	vit upper(int x) { return upper_bound(v.begin(), v.end(), x); }
	int front() { return *v.begin(); }
	int back() { return v[v.size() - 1]; }
	int vsize() { return v.size(); }
	void insert(int x) { v.emplace_back(x); }
}*head;

void split(node *p) {
    node *q = new node();
    q -> next = p -> next; if (q -> next) q -> next -> pre = q;
    p -> next = q, q -> pre = p;
    rop(i, S, p -> vsize()) q -> insert((p -> v)[i]);
    (p -> v).erase(p -> vbegin() + S, p -> vend());
}
void insert(int x, int val) {
	for (node *p = head; p; p = p -> next) {
		if (x - p -> vsize() <= 0) {
			(p -> v).emplace((p -> vbegin()) + x - 1, val);
			if (p -> vsize() >= S * 2)
				split(p);
			return;
		}
		x -= p -> vsize();
	}
}
int query(int x) {
	for (node *p = head; p; p = p -> next) {
		if (x - p -> vsize() <= 0)
			return (p -> v)[x - 1];
		x -= p -> vsize();
	}
}

int main() {
	scanf("%d", &n); S = (int)sqrt(n);
	head = new node(); (head -> v).emplace_back(INF);
	for (int i = 1; i <= n; i ++ ) {
		int val; scanf("%d", &val); 
		insert(i, val);
	}
			
	for (int i = 1; i <= n; i ++ ) {
		int op, l, r, c;
		scanf("%d%d%d%d", &op, &l, &r, &c);
		if (op == 0) insert(l, r);
		else printf("%d\n", query(r));
	}
	
	return 0;
}
posted @ 2023-01-12 16:19  Link-Cut-Y  阅读(308)  评论(1编辑  收藏  举报