您的位置:首页 > 理论基础 > 数据结构算法

模板整理: 部分数据结构

2017-11-10 09:42 495 查看
最重要的内容之一= =

主要整一下线段树,树状数组,st表,平衡树。

主要前3个,第4个是用来乱搞的= =会用set的应该也口译。。。

1.线段树

主要思想是把一个线段从中间分开,分别处理,

然后合并两个区间。

有区间合并性的信息都可以用线段树来维护。

常数偏大,注意数组开4倍防止越界。

还有懒惰标记,处理区间更新的情况。有时候下传标记顺序很重要。

单点修改直接log(n)修改即可。

线段树的性质主要在于区间合并。

例题:区间乘一个数,区间加一个数,询问一个区间的和

luogu3373

区间乘、加分别用两个标记搞搞就好了。

注意下传的时候要先乘法再加法。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll read(){
ll x=(ll)0,f=(ll)1;char ch=getchar();
while (ch<'0' || ch>'9'){if (ch=='-') f=(ll)-1;ch=getchar();}
while (ch>='0' && ch<='9'){x=x*(ll)10+ch-'0';ch=getchar();}
return x*f;
}
const int
N=200005;
ll n,moder,a
;
struct Segment{
ll plus,multi,sum;
bool ifplus,ifmulti;
}tr[N<<2];
void up(int id){
int l=id<<1,r=id<<1|1;
tr[id].sum=(tr[l].sum+tr[r].sum)%moder;
}
void calcmulti(int x,ll tmp){
tr[x].multi=(tr[x].multi*tmp)%moder;
tr[x].sum=(tr[x].sum*tmp)%moder;
tr[x].plus=(tr[x].plus*tmp)%moder;
tr[x].ifmulti=1;
}
void calcplus(int x,ll tmp,int L,int R){
tr[x].plus=(tr[x].plus+tmp)%moder;
tr[x].sum=(tr[x].sum+(tmp*(ll)(R-L+1))%moder)%moder;
tr[x].ifplus=1;
}
void down(int id,int l,int r){
int L=id<<1,R=id<<1|1;
if (tr[id].ifmulti){
calcmulti(L,tr[id].multi);
calcmulti(R,tr[id].multi);
tr[id].multi=(ll)1;
tr[id].ifmulti=0;
}
if (tr[id].ifplus){
int mid=(l+r)>>1;
calcplus(L,tr[id].plus,l,mid);
calcplus(R,tr[id].plus,mid+1,r);
tr[id].plus=(ll)0;
tr[id].ifplus=0;
}
}
void build(int id,int l,int r){
tr[id].multi=(ll)1,tr[id].plus=(ll)0;
tr[id].ifmulti=tr[id].ifplus=0;
if (l==r){
tr[id].sum=a[l];
return;
}
int mid=(l+r)>>1;
build(id<<1,l,mid);
build(id<<1|1,mid+1,r);
up(id);
}
void update_plus(int id,int l,int r,int gl,int gr,ll num){
down(id,l,r);
if (l>=gl && r<=gr){
calcplus(id,num,l,r);
return;
}
int mid=(l+r)>>1;
if (gr<=mid) update_plus(id<<1,l,mid,gl,gr,num); else
if (gl>mid) update_plus(id<<1|1,mid+1,r,gl,gr,num);
else update_plus(id<<1,l,mid,gl,mid,num),
update_plus(id<<1|1,mid+1,r,mid+1,gr,num);
up(id);
}
void update_multi(int id,int l,int r,int gl,int gr,ll num){
down(id,l,r);
4000
if (l>=gl && r<=gr){
calcmulti(id,num);
return;
}
int mid=(l+r)>>1;
if (gr<=mid) update_multi(id<<1,l,mid,gl,gr,num); else
if (gl>mid) update_multi(id<<1|1,mid+1,r,gl,gr,num);
else update_multi(id<<1,l,mid,gl,mid,num),
update_multi(id<<1|1,mid+1,r,mid+1,gr,num);
up(id);
}
ll query(int id,int l,int r,int gl,int gr){
down(id,l,r);
if (l>=gl && r<=gr) return tr[id].sum;
int mid=(l+r)>>1;
if (gr<=mid) return query(id<<1,l,mid,gl,gr)%moder; else
if (gl>mid) return query(id<<1|1,mid+1,r,gl,gr)%moder;
else return (query(id<<1,l,mid,gl,mid)
+query(id<<1|1,mid+1,r,mid+1,gr))%moder;
}
int main(){
int Q;
n=read(),Q=read(),moder=read();
for (int i=1;i<=n;i++) a[i]=read();
build(1,1,n);
int opt,t,g;
while (Q--){
opt=read(),t=read(),g=read();
if (opt==3) printf("%lld\n",query(1,1,n,t,g));
else
if (opt==1) update_multi(1,1,n,t,g,read());
else update_plus(1,1,n,t,g,read());
}
return 0;
}


2.树状数组

x在树状数组里的父亲是x+(x&(-x)),

实质是一种二进制位的操作。

单点修改区间查询非常容易(且代码短)

如果是区间修改单点查询,

可以考虑用差分的思想(把单点查询转化为前缀和查询)

单点修改区间查询:

void update(int x,int y){
while (x<=n) tr[x]+=y,x+=x&(-x);
}
int get(int x){
int y=0;
while (x) y+=tr[x],x-=x&(-x);
return y;
}
int query(int L,int R){
return get(R)-get(L-1);
}


区间修改单点查询:

(注意树状数组意义已经变了,是差分,

也就是说,一开始数组元素存入tr里的应该是a[i]-a[i-1]这个差值)

void updt(int x,int y){
while (x<=n) tr[x]+=y,x+=x&(-x);
}
void update(int L,int R,int num){
updt(L,num),updt(R+1,-num);.
}
int query(int x){
int y=0;
while (x) y+=tr[x],x-=x&(-x);
return y;
}


事实上还有区间修改区间查询的方法,

推荐一下YH大佬的blog

他讲的就是区间修改区间查询的一维和二维情况。

通过式子来分类维护。

3.st表

或者说!倍增!

一个如此精妙的思想~~

st表就是采用了倍增的思路来处理信息,只不过是离线的。

比如一个区间[L,R],可以知道[L,x]和[y,R]内的最值且x>=y

则可以知道[L,R]内的最值

比如区间的最值,树上的LCA问题,都可以由st表来解决。

最简单的例子:求区间的最大/最小值

//注意f[i][0]=a[i]这个初始化
//num=log2(n)
for (int j=1;j<=num;j++)
for (int i=1;i<=n;i++)
if (i+(1<<j)-1>n) break;
else f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);


查询[L,R]的时候,目标就是分出这个x和y,

那么具体如下:

int MIN(int L,int R){
int k=(double)log(R-L+1)/(double)log(2);
if (f[L][k]<f[R-(1<<k)+1][k]) return f[L][k];
return f[R-(1<<k)+1][k];
}


这也说明了两个区间是重合的,求和就不能这样了,

必须得用一个log,这个不多说了。

然后又比如树上的LCA问题,预处理fa[i][0]=pre[i],

pre[i]表示i在树上的父亲。

那么预处理:

//num=log2(n)
for (int j=1;j<=num;j++)
for (int i=1;i<=n;i++)
if (fa[i][j-1]) fa[i][j]=fa[fa[i][j-1]][j-1];


求LCA的过程,就是从深度差入手。

//dep[u]表示u在树中的深度
int LCA(int u,int v){
if (dep[u]<dep[v]) swap(u,v);
int t=dep[u]-dep[v];
for (int i=0;i<=num;i++)
if ((1<<i)&t) u=fa[u][i];
for (int i=num;i>=0;i--)
if (fa[u][i]!=fa[v][i])
u=fa[u][i],v=fa[v][i];
if (u==v) return u;
return fa[u][0];
}


倍增的思想真的很巧妙,不论线性还是树上。

st表可能会有应用到吧…….

4.平衡树

把二叉搜索树平衡化就成了神器平衡树……

这东西似乎没什么好讲的?= =

思路最后的地方大概说说吧。。

例题:luogu3369

//splay
#include<bits/stdc++.h>
#define N 100005
using namespace std;
int Tcnt=0,root=0;
int nn
;
struct Ty{
int son[2],sz,pre,sum;
}tree
;
void up(int u){
tree[u].sz=1+tree[tree[u].son[0]].sz+tree[tree[u].son[1]].sz;
}
void Rotate(int x,int f){
int y=tree[x].pre;
tree[y].son[!f]=tree[x].son[f];
tree[tree[x].son[f]].pre=y;
tree[x].pre=tree[y].pre;
if (tree[x].pre)    tree[tree[y].pre].son[(tree[tree[y].pre].son[1]==y)]=x;
tree[x].son[f]=y;
tree[y].pre=x;
up(x);  up(y);
}
void splay(int x,int goal){
while (tree[x].pre!=goal){
if (tree[tree[x].pre].pre==goal) Rotate(x,tree[tree[x].pre].son[0]==x);
else{
int y=tree[x].pre,z=tree[y].pre;
int f=(tree[z].son[0]==y);
if (tree[y].son[f]==x) Rotate(x,!f),Rotate(x,f);
else Rotate(y,f),Rotate(x,f);
}
}
up(x);
if (!goal) root=x;
}
void insert(int i,int x){
nn[i]=x;
int z=0,y=root;
while (y){
z=y;
y=tree[y].son[(nn[i]>=nn[y])];
}
tree[i].pre=z;
if (!z) root=i;
else    tree[z].son[(nn[i]>=nn[z])]=i;
up(i); z=tree[i].pre;
while (z)   up(z),z=tree[z].pre;
}
int find(int x){
int i=root;
while (nn[i]!=x){
if (nn[i]<=x)   i=tree[i].son[1];
else    i=tree[i].son[0];
}
return i;
}
void delet(int x){
int j,i=find(x);
while (tree[i].son[0] || tree[i].son[1]){
if (!tree[i].son[0])    j=tree[i].son[1];
else    j=tree[i].son[0];
splay(j,tree[i].pre);
}
j=tree[i].pre;
tree[tree[i].pre].son[tree[tree[i].pre].son[1]==i]=0;
tree[i].pre=0;
tree[i].son[0]=tree[i].son[1]=0;
tree[i].sz=0;
while (j) up(j),j=tree[j].pre;
}
int rank(int x){
int y=root,z=0;
while (1){
if (!y) return 0;
if (nn[y]==x)   return tree[tree[y].son[0]].sz+1+z;
if (nn[y]>x)    y=tree[y].son[0];
else    z+=tree[tree[y].son[0]].sz+1,y=tree[y].son[1];
}
}
int nrank(int x){
int z=root;
while (x){
int k=tree[tree[z].son[0]].sz;
if (x==k+1) return nn[z];
if (x<=k)   z=tree[z].son[0];
else    z=tree[z].son[1],x-=k+1;
}
return nn[z];
}
int prre(int x){
int y=root,z=0;
while (1){
if (!y) return nn[z];
if (nn[y]>=x)   y=tree[y].son[0];
else{
z=y;
y=tree[y].son[1];
}
}
}
int neext(int x){
int y=root,z=0;
while (1){
if (!y) return nn[z];
if (nn[y]>x){
if (nn[y]!=x)   z=y;
y=tree[y].son[0];
}   else    y=tree[y].son[1];
}
}
int main(){
int n;Tcnt=0;
scanf("%d",&n);
while (n--){
int opt,x;
scanf("%d%d",&opt,&x);
if (opt==1) insert(++Tcnt,x);
if (opt==2) delet(x);
if (opt==3) printf("%d\n",rank(x));
if (opt==4) printf("%d\n",nrank(x));
if (opt==5) printf("%d\n",prre(x));
if (opt==6) printf("%d\n",neext(x));
}
return 0;
}


//treap
#include<bits/stdc++.h>
using namespace std;
const int
N=100005;
int root,tot,ans;
struct treap{
int sz,occ,l,r,val,rnd;
}tr
;
void up(int k){
tr[k].sz=tr[tr[k].l].sz+tr[tr[k].r].sz+tr[k].occ;
}
void rturn(int &k){
int t=tr[k].l;tr[k].l=tr[t].r,tr[t].r=k;
up(k),k=t,up(k);
}
void lturn(int &k){
int t=tr[k].r;tr[k].r=tr[t].l,tr[t].l=k;
up(k),k=t,up(k);
}
void insert(int &k,int x){
if (!k){
k=++tot;
tr[k]=(treap){1,1,0,0,x,rand()};
return;
}
tr[k].sz++;
if (tr[k].val==x){tr[k].occ++;return;}
if (x<tr[k].val){
insert(tr[k].l,x);
if (tr[k].rnd>tr[tr[k].l].rnd) rturn(k);
} else{
insert(tr[k].r,x);
if (tr[k].rnd>tr[tr[k].r].rnd) lturn(k);
}
}
void del(int &k,int x){
if (!k) return;
if (tr[k].val==x){
if (tr[k].occ>1){tr[k].sz--,tr[k].occ--;return;}
if (!tr[k].l || !tr[k].r){k=tr[k].l^tr[k].r;return;}
if (tr[tr[k].l].rnd<tr[tr[k].r].rnd) rturn(k);
else lturn(k);
del(k,x);
return;
}
tr[k].sz--;
if (x<tr[k].val) del(tr[k].l,x);
else del(tr[k].r,x);
}
int query_rank(int &k,int x){
if (!k) return 0;
if (tr[k].val==x) return tr[tr[k].l].sz+1;
if (x<tr[k].val) return query_rank(tr[k].l,x);
else return query_rank(tr[k].r,x)+tr[k].occ+tr[tr[k].l].sz;
}
int query_num(int &k,int x){
if (!k) return 0;
if (tr[tr[k].l].sz>=x) return query_num(tr[k].l,x);
else if (tr[tr[k].l].sz+tr[k].occ<x)
return query_num(tr[k].r,x-tr[k].occ-tr[tr[k].l].sz);
return tr[k].val;
}
void query_pre(int &k,int x){
if (!k) return;
if (tr[k].val<x){ans=tr[k].val,query_pre(tr[k].r,x);}
else query_pre(tr[k].l,x);
}
void query_suc(int &k,int x){
if (!k) return;
if (tr[k].val>x){ans=tr[k].val,query_suc(tr[k].l,x);}
else query_suc(tr[k].r,x);
}
int main(){
srand(time(NULL));
int n,opt,x;
root=tot=0;
scanf("%d",&n);
while (n--){
scanf("%d%d",&opt,&x);
if (opt==1) insert(root,x);
if (opt==2) del(root,x);
if (opt==3) printf("%d\n",query_rank(root,x));
if (opt==4) printf("%d\n",query_num(root,x));
if (opt==5){query_pre(root,x);printf("%d\n",ans);}
if (opt==6){query_suc(root,x);printf("%d\n",ans);}
}
return 0;
}


splay基于旋转操作,通过时间复杂度分析之后是不错的O(log(n))

treap基于随机令其期望O(log(n))

splay常数很大,所以能用treap就用treap吧。

很多线段树能干的一些事情,平衡树也是可以轻松解决的。

不过考这东西我口也屎嘞。。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: