模板整理: 部分数据结构
2017-11-10 09:42
495 查看
最重要的内容之一= =
主要整一下线段树,树状数组,st表,平衡树。
主要前3个,第4个是用来乱搞的= =会用set的应该也口译。。。
1.线段树
主要思想是把一个线段从中间分开,分别处理,
然后合并两个区间。
有区间合并性的信息都可以用线段树来维护。
常数偏大,注意数组开4倍防止越界。
还有懒惰标记,处理区间更新的情况。有时候下传标记顺序很重要。
单点修改直接log(n)修改即可。
线段树的性质主要在于区间合并。
例题:区间乘一个数,区间加一个数,询问一个区间的和
luogu3373
区间乘、加分别用两个标记搞搞就好了。
注意下传的时候要先乘法再加法。
2.树状数组
x在树状数组里的父亲是x+(x&(-x)),
实质是一种二进制位的操作。
单点修改区间查询非常容易(且代码短)
如果是区间修改单点查询,
可以考虑用差分的思想(把单点查询转化为前缀和查询)
单点修改区间查询:
区间修改单点查询:
(注意树状数组意义已经变了,是差分,
也就是说,一开始数组元素存入tr里的应该是a[i]-a[i-1]这个差值)
事实上还有区间修改区间查询的方法,
推荐一下YH大佬的blog,
他讲的就是区间修改区间查询的一维和二维情况。
通过式子来分类维护。
3.st表
或者说!倍增!
一个如此精妙的思想~~
st表就是采用了倍增的思路来处理信息,只不过是离线的。
比如一个区间[L,R],可以知道[L,x]和[y,R]内的最值且x>=y
则可以知道[L,R]内的最值
比如区间的最值,树上的LCA问题,都可以由st表来解决。
最简单的例子:求区间的最大/最小值
查询[L,R]的时候,目标就是分出这个x和y,
那么具体如下:
这也说明了两个区间是重合的,求和就不能这样了,
必须得用一个log,这个不多说了。
然后又比如树上的LCA问题,预处理fa[i][0]=pre[i],
pre[i]表示i在树上的父亲。
那么预处理:
求LCA的过程,就是从深度差入手。
倍增的思想真的很巧妙,不论线性还是树上。
st表可能会有应用到吧…….
4.平衡树
把二叉搜索树平衡化就成了神器平衡树……
这东西似乎没什么好讲的?= =
思路最后的地方大概说说吧。。
例题:luogu3369
splay基于旋转操作,通过时间复杂度分析之后是不错的O(log(n))
treap基于随机令其期望O(log(n))
splay常数很大,所以能用treap就用treap吧。
很多线段树能干的一些事情,平衡树也是可以轻松解决的。
不过考这东西我口也屎嘞。。
主要整一下线段树,树状数组,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吧。
很多线段树能干的一些事情,平衡树也是可以轻松解决的。
不过考这东西我口也屎嘞。。
相关文章推荐
- Java数据结构与算法之选择排序_动力节点Java学院整理
- 数据结构基础算法整理归纳:插入排序(三)
- Linux协议栈网桥部分之主要数据结构
- 相同表结构部分数据差异整体更新
- 部分题目整理:大数据
- 数据文件结构分析——第二部分
- 数据结构中几种排序算法的整理
- 整理部分java代码和oracle,mysql,sql server对比数据类型
- Informix数据表结构分析资料整理之约束查询代码
- 数据结构整理(一) —— 链表的各种操作
- Java数据结构与算法之栈_动力节点Java学院整理
- 【数据结构与算法】 利用哈夫曼树进行文件压缩 (部分借鉴网上内容)
- [redis读书笔记] 第一部分 数据结构与对象 字典
- Java数据结构内容整理
- STL 数据结构部分原创入门教程,要详细资料请百度
- [redis读书笔记] 第一部分 数据结构与对象 压缩列表
- 数据文件结构分析——第三部分
- 数据结构——向量——向量模板源码
- sk_buff 整理笔记(一、数据结构)
- 数据结构之实用单链表(参考整理严蔚敏数据结构)