块状链表,可以说是一种很犯规的数据结构
支持动态的序列加入删除,询问区间和之类的操作
同时拥有数组和链表的优点:
数组: 所有数据在内存中是紧凑储存的,优点是定位快:O(1)
链表: 通过指针将不同位置的元素链接起来:修改快 O(1)
总的来说重要操作只有几个:
定位,分裂,插入,合并
虽然说是链表,但是在实现的时候,我们还是用数组模拟
块状链表的空间比较奇怪,每个块的大小在
sqrt(n)2−2sqrt(n)
s
q
r
t
(
n
)
2
−
2
s
q
r
t
(
n
)
,大多数情况下会偏大
初始化
数组的空间并不优秀,所以我们要时刻节俭,队列q中记录的就是可用的结点编号
因此我们申请结点和删除结点都需要在队列q的基础上进行
void init()
{
for (int i=1;i<=blocknum;i++) q.push(i);
a[0].len=0; a[0].nxt=-1;
}
int newnode()
{
int t=q.front(); q.pop();
return t;
}
void delnode(int t)
{
q.push(t);
}
在位置p插入一段数据,第一步就是定位
(默认链表起点就是0,邻接表遍历,每一个结点中下标从0开始)
void find(int &pos,int &now) //从第一个块开始搜索,搜索位置pos所属的块的编号
{
for (now=0;a[now].nxt!=-1&&pos>a[now].len;now=a[now].nxt) pos-=a[now].len;
}
定位完成后,我们需要将该块分裂开
不是特别理解memcpy的用法?
简单来说,如果我们需要把
a
a
数组从位置开始的长度
len
l
e
n
复制到
b
b
数组位置,则
(亲测了一下,发现如果是数组类型只能全部复制,只有字符串可以部分copy,难道是我的姿势不对?)
void fillnode(int pos,int n,char data[],int nxt)
{
a[pos].nxt=nxt; a[pos].len=n;
memcpy(a[pos].data,data,n); //将data中前n个元素复制到块pos中
}
void divide(int pos,int p)
{
if (a[pos].len==p) return;
int t=newnode();
fillnode(t,a[pos].len-p,a[pos].data+p,a[pos].nxt);
a[pos].nxt=t;
a[pos].len=p;
}
每次插入删除之后,我们都要合并一下小结点
void maintain(int pos) //当前块与后一个块合并
{
int t;
for (;pos!=-1;pos=a[pos].nxt)
for (t=a[pos].nxt;t!=-1&&a[pos].len+a[t].len<blocksize;t=a[t].nxt)
{
memcpy(a[pos].data+a[pos].len,a[t].data,a[t].len);
a[pos].len+=a[t].len;
a[pos].nxt=a[t].nxt;
delnode(t);
}
}
大体上来说就是这些东西
代码中有些小细节,只要理解到位就没什么问题了
记住块里的下标从0开始,牵扯到find和divide中的pos一定要大于0
const int N=1<<25;
const int blocksize=20000;
const int blocknum=N/blocksize*3;
int n;
char s[N+10];
struct node{
char data[blocksize+10];
int len,nxt;
};
node a[blocknum+10];
queue<int> q;
int newnode() {
int t=q.front(); q.pop();
return t;
}
void delnode(int t) {
q.push(t);
}
void find(int &pos,int &now) {
for (now=0;now!=-1&&a[now].len<pos;now=a[now].nxt) pos-=a[now].len;
//保证pos>0: a[now].len<pos
}
void fillnode(int pos,int n,char *data,int nxt) {
int t=newnode();
memcpy(a[t].data,data,n); //memcpy就说明有结点发生变化,需要维护len和nxt
a[t].nxt=nxt;
a[t].len=n;
}
void divide(int now,int pos) {
if (pos==a[now].len) return;
int t=newnode();
fillnode(t,a[data].len-pos,a[now].data+pos,a[now].nxt);
a[now].nxt=t;
a[now].len=pos;
}
void maintain(int now) {
for (;now!=-1;now=a[now].nxt)
for (int t=a[now].nxt;t!=-1&&a[now].len+a[t].len<blocksize;t=a[t].nxt) {
memcpy(a[now].data+a[now].len,a[t].data,a[t].len);
a[now].len+=a[t].len;
a[now].nxt=a[t].nxt;
delnode(t); //删除结点
}
}
void insert(int pos,int n) {
int now,i=0,t;
find(pos,now);
divede(now,pos);
for (i=0;i+blocksize<=n;i+=blocksize) { //i+blocksize<=n
int t=newnode();
fillnode(t,blocksize,s+i,a[now].nxt);
a[now].nxt=t;
now=t;
}
if (i<n) {
int t=newnode();
fillnode(t,n-i,s+i,a[now].nxt);
a[now].nxt=t;
}
maintain(now);
}
void del(int pos,int n) {
int now,i,t;
find(pos,now);
divide(now,pos);
for (i=a[now].nxt;i!=-1&&a[i].len<n;i=a[i].nxt)
n-=a[i].len;
divide(i,n); //保证n>0: a[i].len<n
i=a[i].nxt;
for (t=a[now].nxt;t!=i;t=a[now].nxt) { //注意t的循环: t=a[now].nxt
a[now].nxt=a[t].nxt;
delnode(t);
}
maintain(now);
}
void get(int pos,int n) {
int now,i,t;
find(now,pos);
i=min(n,a[now].len-pos);
memcpy(s,a[now].data+pos,i);
for (t=a[now].nxt;t!=-1&&i+a[t].len<=n;t=a[t].nxt) {
memcpy(s+i,a[t].data,a[t].len);
i+=a[t].len;
}
if (i<n) memcpy(s+i,a[t].data,n-i);
s[n]=0;
}
void init() {
for (int i=1;i<=blocknum;i++) q.push(i);
a[0].len=0; a[0].nxt=-1;
}
只写过一道个其他算法结合的题目:块状链表+并查集