块状链表的超高速实现
很久以前写的文章了,有空补一下。
块状链表是什么,这是一个可以用来代替平衡树的东西。平衡树一般很长,作为手残选手,自然不可能像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})\) 的。
接下来先看一道例题。
【分析】
这里可以直接维护一堆 \(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\)