模板整理: 部分数据结构


最重要的内容之一= =
主要整一下线段树,树状数组,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[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 100005
using 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吧。
很多线段树能干的一些事情,平衡树也是可以轻松解决的。
不过考这东西我口也屎嘞。。

猜你喜欢

转载自blog.csdn.net/ThinFatty/article/details/78496486