块状链表(STL rope)

块状链表(STL rope)
首先介绍一下块状链表。

我们都知道:
数组 具有 O(1)的查询时间,O(N)的删除,O(N)的插入。
链表 具有 O(N)的查询时间,O(1)的删除,O(1)的插入。
既然数组和链表各有优劣,那么我们为何不将链表和数组组合起来,一起来均摊时间呢?

做法就是维护一个链表,链表中的每个单元包含一段数组,以及这个数组中的数据个数。每个链表中的数据连起来就是整个数据。

  • 时间复杂度证明:

设链表的长度为a,每个单元中数组的长度为b。那么无论是插入还是删除,在寻址的时候都要遍历整个链表,复杂度是O(a);
对于插入操作,如果直接在链表中加入一个新的单元,时间复杂度是O(1),如果要在一个单元内插入一个新的数,那么则需要移动数组中的数据,复杂度是O(b),总的复杂度是O(a + b)。
对于删除操作,可能会涉及多个连续的单元,如果一个单元中的所有数据均要删除,直接删除这个单元,复杂度是O(1),如果只删除部分的数据,则要移动数组中的数据,复杂度是O(b)。总的复杂度是O(a + b)。

因为ab = n,取a = b = √n,则总的复杂度是O(2√n)= O(√n)

问题是如何维护a和b大致等于√n?
插入操作:

每个链表节点的数据是一个数组块,那么问题来了,我们是根据什么将数组切开呢?总不能将所有的数据都放在一个链表的节点吧,那就退化成数组了。在理想的情况下,为了保持√N的数组个数,所以我们定了一个界限2√N,当链表中的节点数组的个数超过2√N的时候,当下次插入数据的时候,我们有两种做法:

① 在元素的数组插入处,将当前数组切开,插入元素处之前为一个链表节点,插入元素后为一个链表节点。

② 将元素插入数组后,将数组从中间位置切开。

删除操作:

跟插入道理一样,既然有裂开,就有合并,我们同样定义一个界限值√n/2,当链表节点的数组个数小于这个界限值的时候,就需要将此节点和后面的链表节点进行合并。

执行上述维护操作需要移动数组中的数据,复杂度是O(b),对于单元的分割和合并均是O(1)的,总的复杂度是O(b)的。这样,维护操作并不会使总复杂度增加。最终得到一个复杂度是O(√n)的数据结构。

因为块状链表的代码太过于冗长,所以如果实在要用到的话我推荐使用STL库里的rope(C++11及以上可使用),相关的rope讲解:

Rope大法(可持久化平衡树)
【可持久化线段树?!】rope史上最全详解

这里注意rope是支持"+"操作的。

题目

问题很简单,但是需要涉及到区间的操作,用rope暴力就行。复杂度为O(n√n)。

#include<bits/stdc++.h>
#include <ext/rope>
#define ll long long
#define inf 0x3f3f3f3f
#define endl '\n'
#define IOS std::ios::sync_with_stdio(false),cin.tie(0), cout.tie(0)

using namespace std;
using namespace __gnu_cxx;

const int N = 1e5 + 10;

rope<int>r;
int n, m, a, b;


int main()
{
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; i ++) r.push_back(i);
	while(m --)
	{
		scanf("%d%d", &a, &b);
		r = r.substr(a - 1, b) + r.substr(0, a - 1) + r.substr(a + b - 1, n - a - b + 1);   // -1是因为rope的下标从0开始
	}
	for(int i = 0; i < n; i ++)
		printf("%d ", r[i]);
	return 0;
}

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值