模板整理: 部分数据结构

来源:互联网 发布:软件测试员培训 编辑:程序博客网 时间:2024/06/07 13:30


最重要的内容之一= =
主要整一下线段树,树状数组,st表,平衡树。
主要前3个,第4个是用来乱搞的= =会用set的应该也口译。。。

1.线段树
主要思想是把一个线段从中间分开,分别处理,
然后合并两个区间。
有区间合并性的信息都可以用线段树来维护。
常数偏大,注意数组开4倍防止越界。
还有懒惰标记,处理区间更新的情况。有时候下传标记顺序很重要。
单点修改直接log(n)修改即可。
线段树的性质主要在于区间合并。
例题:区间乘一个数,区间加一个数,询问一个区间的和
luogu3373
区间乘、加分别用两个标记搞搞就好了。
注意下传的时候要先乘法再加法。

#include<bits/stdc++.h>#define ll long longusing 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[N];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);    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 100005using namespace std;int Tcnt=0,root=0;int nn[N];struct Ty{    int son[2],sz,pre,sum;}tree[N];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[N];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吧。
很多线段树能干的一些事情,平衡树也是可以轻松解决的。
不过考这东西我口也屎嘞。。

原创粉丝点击