块状链表的超高速实现

很久以前写的文章了,有空补一下。

块状链表是什么,这是一个可以用来代替平衡树的东西。平衡树一般很长,作为手残选手,自然不可能像lxl那样快速的3分钟打完平衡树。

但是有了块状链表就不一样,这个东西可以让你也3分钟写完块状链表。

块状链表是用分块套链表实现的。但是我并没写过这个东西,我是根据w33z的日报去写的。为啥?我写的链表很丑。

有一个好东西叫作 \(vector\) ,我们都知道的是 \(vector\) 中也有一个叫做插入的东西。具体的来说是怎么做的呢?就是下面这样:v.insert(v.begin()+x,y) 表示的就是在第x个位置哪里插入一个值为 \(y\) 的数。

另外的,他还可以支持删除操作与修改操作,所以这让我们感觉到这就像是一个链表一样。但实际他的复杂度还是为 \(O(n)\) 的。

我们考虑去优化这个东西,优化这个东西,那我们就是要把这个 \(vector\) 中所保存的数量变小,这样就会让他看起来比较友好。这让我们想到了分块。

具体怎么做?建立多个 \(vector\) 来表示一段序列。

每个 \(vector\) 中保存的数不超过 \(s\) ,那么总体就有 $ \dfrac{n}{s} $ 个 \(vector\)

建立出 \(vector\) 之后还有一些很不错的运用。

区间第k大查询

插入与删除一个数

单点修改,区间修改(再套一个分块)

我们如果将 $ s= \sqrt{n}$ 。
那么可以证明的是总复杂度其实是约为 \(O(n \sqrt{n})\) 的。

接下来先看一道例题。

#6282. 数列分块入门 6

【分析】

这里可以直接维护一堆 \(vector\) ,然后大力插入查找。

为了保证我们复杂度的优秀,当某一块的元素为选定 \(siz\) 的大小
两倍多时,考虑 \(O(n)\) 重构块。看着很不优秀,但是复杂度很不错。

因为设块长为 \(\sqrt{n}\) 时,必须插入 \(\sqrt{n}\)
个数才会重构一次。而最多全是插入操作时,就会重构 \(\sqrt{n}\) 次,于是复杂度就是铁打不动的 \(O(n\sqrt{n})\)

【代码】

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5;
int n,bel,tmp[N],x;
const int siz=300;
vector<int>v[N];
vector<int>::iterator it;
int fin(int &kth){
	for(int i=1;i<=bel;i++){
	kth-=v[i].size();
	if(kth<=0){kth+=v[i].size();return i;}
	}
}
void rebuild(){
	int tot=0;
	for(int i=1;i<=bel;i++){
		for(it=v[i].begin();it!=v[i].end();it++) tmp[++tot]=*it;
		v[i].clear();
	}
	for(int i=1;i<=tot;i++){bel=(i-1)/siz+1;v[bel].push_back(tmp[i]);}
}
void insert(int p,int val){
	int id=fin(p);v[id].insert(v[id].begin()+p-1,val);
	if(v[id].size()>2*siz) rebuild();
}
signed main(){
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>n;
	for(int i=1;i<=n;i++){
		cin>>x;bel=(i-1)/siz+1;
		v[bel].push_back(x);
	}
	for(int i=1;i<=n;i++){
		int op,l,r,c;cin>>op>>l>>r>>c;
		if(!op){insert(l,r);}
		else{int id=fin(r); cout<<v[id][r-1]<<"\n";}
	}
	return 0;
}

然后我们发现我们甚至没有怎么压行,还是很短,只有37行。我写这段代码只用了6分钟。练习一下就可以进步到4分钟左右。

然后神仙 \(w33z\) 的代码...还是跳过吧

因为 \(noip\) 不支持 \(c++11\)

posted @ 2021-07-08 22:04  Pitiless0514  阅读(280)  评论(8编辑  收藏  举报