块状链表

块状链表

对于线性表,可以 \(O(1)\) 的访问,但是插入和删除操作是 \(O(n)\)

对于链表,可以 \(O(1)\) 的进行插入和删除,但是是 \(O(n)\) 的访问。

于是本着分块的思想,有了块状链表 。

大概长这个样子。每个块的大小数量级在 $ O(\sqrt n)$ , 块数的量级 $ O(\sqrt n)$

主要有以下几种操作:

插入

  1. 分裂节点 $ O(\sqrt n)$

  2. 在分裂点插入 $ O(\sqrt n)$

删除

  1. 删除开头节点的后半部分 $ O(\sqrt n)$

  2. 删除中心完整节点 $ O(\sqrt n)$

  3. 删除结尾节点的前半部分 $ O(\sqrt n)$

合并

为了保证正确的复杂度,要不定期的进行合并操作。

所谓合并操作,就是从第一个块开始,如果把下一个块合并过来之后大小不大于 $ O(\sqrt n)$,就把两个块合并

若没有合并操作,则可能会有很多小块,导致 TLE

例题 P4008 [NOI2003] 文本编辑器

//别开 long long

#include <bits/stdc++.h>

#define rint register int
#define endl '\n'

using namespace std;

const int N = 2e3;
const int M = 2e3 + 1e1;
const int Q = 2e6 + 1e1;

int n, x, y;
struct Node
{
    char s[N + 1];
    int c, l, r;
} p[M];
char str[Q];
int q[M], tt; // 内存回收

void move(int k) // 移到第k个字符后面
{
    x = p[0].r;
    while (k > p[x].c)
        k -= p[x].c, x = p[x].r;
    y = k - 1;

    return;
}

void add(int x, int u) // 将节点u插到节点x的右边
{
    p[u].r = p[x].r;
    p[p[u].r].l = u;
    p[x].r = u;
    p[u].l = x;
    return;
}

void del(int u) // 删除节点u
{
    p[p[u].l].r = p[u].r;
    p[p[u].r].l = p[u].l;
    p[u].l = p[u].r = p[u].c = 0; // 清空节点u
    q[++tt] = u;                  // 回收节点u
    return;
}

void insert(int k) // 在光标后插入k个字符
{
    if (y < p[x].c - 1) // 从光标处分裂
    {
        int u = q[tt--]; // 新建一个节点
        for (rint i = y + 1; i < p[x].c; i++)
            p[u].s[p[u].c++] = p[x].s[i];
        p[x].c = y + 1;
        add(x, u);
    }
    
    int cur = x;

    for (rint i = 0; i < k;)
    {
        int u = q[tt--]; // 创建一个新的块
        while (p[u].c < N && i < k)
            p[u].s[p[u].c++] = str[i++];
        add(cur, u);
        cur = u;
    }
    return;
}

void remove(int k) // 删除光标后的k个字符
{
    if (p[x].c - 1 - y >= k) // 节点内删
    {
        for (rint i = y + k + 1, j = y + 1; i < p[x].c; i++, j++)
            p[x].s[j] = p[x].s[i];
        p[x].c -= k;
    }

    else
    {
        k -= p[x].c - y - 1; // 删除当前节点的剩余部分
        p[x].c = y + 1;

        while (p[x].r && k >= p[p[x].r].c)
        {
            int u = p[x].r;
            k -= p[u].c;
            del(u);
        }

        int u = p[x].r; // 删除结尾节点的前半部分

        for (rint i = 0, j = k; j < p[u].c; i++, j++)
            p[u].s[i] = p[u].s[j];

        p[u].c -= k;
    }
    return;
}

void get(int k) // 返回从光标开始的k个字符
{
    if (p[x].c - 1 - y >= k) // 节点内返回
    {
        for (rint i = 0, j = y + 1; i < k; i++, j++)
            putchar(p[x].s[j]);
    }

    else
    {
        k -= p[x].c - y - 1;

        for (rint i = y + 1; i < p[x].c; i++)
            putchar(p[x].s[i]); // 输出当前节点的剩余部分

        int cur = x;

        while (p[cur].r && k >= p[p[cur].r].c)
        {

            int u = p[cur].r;
            for (int i = 0; i < p[u].c; i++)
                putchar(p[u].s[i]);
            k -= p[u].c;
            cur = u;
        }

        int u = p[cur].r;

        for (rint i = 0; i < k; i++)
            putchar(p[u].s[i]);
    }
    puts("");
    return;
}

void prev() // 光标向前移动一位
{
    if (!y)
    {
        x = p[x].l;
        y = p[x].c - 1;
    }
    else
        y--;
    return;
}

void next() // 光标向后移动一位
{
    if (y < p[x].c - 1)
        y++;
    else
    {
        x = p[x].r;
        y = 0;
    }
    return;
}

void merge() // 将长度较短的相邻节点合并,保证块状链表时间复杂度的核心
{
    for (rint i = p[0].r; i; i = p[i].r)
    {
        while (p[i].r && p[i].c + p[p[i].r].c < N)
        {
            int r = p[i].r;
            for (int j = p[i].c, k = 0; k < p[r].c; j++, k++)
                p[i].s[j] = p[r].s[k];
            if (x == r)
                x = i, y += p[i].c; // 更新光标的位置
            p[i].c += p[r].c;
            del(r);
        }
    }
    return;
}

signed main()
{
    for (rint i = 1; i < M; i++)
        q[++tt] = i;

    cin >> n;
    char op[10];

    str[0] = '>';
    insert(1); // 插入哨兵
    move(1);   // 将光标移动到哨兵后面

    while (n--)
    {
        int a;
        cin >> op;

        if (!strcmp(op, "Move"))
        {
            cin >> a;
            move(a + 1);
        }

        else if (!strcmp(op, "Insert"))
        {
            cin >> a;
            int i = 0, k = a;

            while (a)
            {
                str[i] = getchar();
                if (str[i] >= 32 && str[i] <= 126)
                    i++, a--;
            }

            insert(k);
            merge();
        }

        else if (!strcmp(op, "Delete"))
        {
            cin >> a;
            remove(a);
            merge();
        }

        else if (!strcmp(op, "Get"))
        {
            cin >> a;
            get(a);
        }

        else if (!strcmp(op, "Prev"))
            prev();

        else
            next();
    }

    return 0;
}
posted @ 2022-08-05 11:57  PassName  阅读(330)  评论(0编辑  收藏  举报