其他分享
首页 > 其他分享> > 块状链表 学习笔记

块状链表 学习笔记

作者:互联网

感谢大佬 @w33z8kqrqk8zzzx33

用块状链表实现平衡树,是怎么回事呢?快来跟小编看看吧~

前置知识

vector 的部分用法

vector 就是变长数组。由于 STL 制作者们八仙过海,所以它的一些操作常数极小(相比于普通数组)。

首先注意,vec 的第一个位置下标是 0;而且大多函数都是左闭右开的(就像 sort 一样,你要写 sort(a+1,a+n+1) 而不是 sort(a+1,a+n),就是因为 sort(l,r) 的范围是 \([l,r)\) 即 \([l,r-1]\))。

平衡树题意

维护一个多重集合,需支持下列操作:

  1. 插入或删除一个数 \(x\);
  2. 查询 \(x\) 排名;
  3. 查询排名为 \(x\) 的数字是什么;
  4. 查询 \(x\) 的前驱/后继。

块状链表

我们想到一个暴力做法:维护一个 vec,使得里面的数字都是从小到大排好序的。

对于四个操作:

  1. 二分 \(x\) 应该在的位置,暴力插入/删除。
  2. 二分 \(x\) 应该在的位置。
  3. cout<<vec[x]
  4. 二分 \(x\) 应该在的位置。

瓶颈显然在于操作 1 ,是小常数 \(O(n)\)。

那么考虑分块。为了方便,设 \(B=\sqrt n\),分成了 B 个大小为 B 的块。

然后二分 \(x\) 的位置,就可以转为另一个方法:

依次遍历每个块,看一下块尾的元素是否小于 \(x\)。如果,则 \(x\) 的位置就在这个块中,在块内二分即可。这是 \(O(B)\) 的哟~

暴力插入/删除的话,也变成了小常数 \(O(B)\)(实测跑的飞快,就像发疯了的 \(O(\log n)\) 一样!)。

但是如果我们不停地在同一个块进行插入,这个块变得很大,会退化成 \(O(n)\)。

解决方案是,如果有一个块的块长大于 \(B\) 了,那么就把它们掰成两个 \(\frac{B}{2}\) 的块

这样保证时间复杂度了,但是还有一些细节:

  1. 为了仍能保证块的大小顺序(即把所有块合成一起时,仍是从小到大排序的),我们需要用链表把所有的块串起来,这样掰开块的时候就是 \(O(1)\) 的了。
  2. 因为链表不支持定点访问,操作 3 的方法要改变了。设立当前排名 \(cnt\),我们一次遍历每一个块,就让 \(cnt\) 增加块的大小。如果 \(cnt\ge x\) 了,代表就在当前块中,块内二分即可。
  3. 一开始把数列加进去的时候,是 \(\frac{B}{2}\) 个为一组,放进块中。

代码

题目 【模板】普通平衡树

链表可以直接用 vector 代替,也就是 vecvec!!1

代码非常短,于是块状链表也被称作 “五分钟平衡树”。

#include<bits/stdc++.h>
#define rep(i,x,y) for(int i=x;i<=y;++i)
#define ltor(id) kzlb[id].begin(),kzlb[id].end() 
#define iter vector <int> ::iterator 
#define pb emplace_back
using namespace std;
const int n7=101234,inf=1e7+1;
int T,B;
vector < vector <int> > kzlb;

int rd(){
	int shu=0;bool fu=0;char ch=getchar();
	while( !isdigit(ch) ){if(ch=='-')fu=1;ch=getchar();}
	while( isdigit(ch) )shu=(shu<<1)+(shu<<3)+ch-'0',ch=getchar();
	return fu?-shu:shu;
}

void wt(int z){
	if(z<0)putchar('-'),z=-z;
	if(z>9)wt(z/10);
	putchar(z%10+'0');
}

int ffind(int x){
	rep(i,0,kzlb.size()-1){
		if(kzlb[i].back()>=x)return i;
	}
}

void isert(int x){
	int id=ffind(x);
	kzlb[id].emplace(lower_bound(ltor(id),x),x);
	if(kzlb[id].size()>B){
		kzlb.emplace(kzlb.begin()+id+1,kzlb[id].begin()+B/2, kzlb[id].end() );
		kzlb[id].erase(kzlb[id].begin()+B/2, kzlb[id].end() );
	}
}

void erase(int x){
	int id=ffind(x);
	kzlb[id].erase( lower_bound(ltor(id),x) );
	if( !kzlb[id].size() )kzlb.erase(kzlb.begin()+id);
}

int Qkth(int x){
	rep(i,0,kzlb.size()-1){
		if(kzlb[i].size()>=x)return kzlb[i][x-1];
		else x-=kzlb[i].size();
	}
}

int Qnum(int x){
	int cnt=0;
	rep(i,0,kzlb.size()-1){
		if(kzlb[i].back()>=x){
			cnt+=lower_bound(ltor(i),x)-kzlb[i].begin();
			return cnt+1;
		}
		else cnt+=kzlb[i].size();
	}
}

int Qpre(int x){
	int id=ffind(x);
	iter wei=lower_bound(ltor(id),x);
	if( wei==kzlb[id].begin() )return kzlb[id-1].back();
	else return *--wei;
}

int Qnxt(int x){
	int id=ffind(x);
	iter wei=upper_bound(ltor(id),x);
	//左闭右开,所以wei=end表示找不到 
	if( wei==kzlb[id].end() )return kzlb[id+1].front();
	else return *wei;
}

int main(){
	kzlb.pb();
	kzlb.back().pb(inf);
	T=rd(),B=sqrt(T);
	while(T--){
		int sys=rd(),x=rd();
		if(sys==1)isert(x);
		if(sys==2)erase(x);
		if(sys==3)wt( Qnum(x) ),putchar('\n');
		if(sys==4)wt( Qkth(x) ),putchar('\n');
		if(sys==5)wt( Qpre(x) ),putchar('\n');
		if(sys==6)wt( Qnxt(x) ),putchar('\n');
	} 
	return 0;
}

标签:kzlb,int,wei,笔记,链表,块状,vec,id,size
来源: https://www.cnblogs.com/BlankAo/p/14724158.html