【数据结构】【可持久化数据结构--线段树】

可持久化数据结构就是让某种数据结构可以持久的访问以前的历史版本,同时也可以从某个历史版本再建一个新的版本的数据结构,比如可持久化线段树,并查集,treap。
正常的线段树长这个样子。

但是如果我对这棵树进行一些奇奇怪怪的操作那么我们就无法查询以前的版本了,比如说我们要查询区间第k大,那么我们可以通过查找树找到第k大的数,但是对于区间第k大就不行了,所以我们要把每个历史版本都存下了,很明显,直接存空间是会炸的,但是我们可以利用空间,说以我们可以把不同历史版本的相同部分利用起来,于是各个历史版本之间就会有一些共用的节点。比如我把上面这颗线段树先整成一个空树,再加一个2,就变成了下面这个图
这里写图片描述
在这里我们新加了一个树根,因此我们要把每一个历史版本的数根用root[MAXN]存下来,由于在这个问题上我们只需要把2的所有父节点建出来,因此插入第一个元素后,土红色是新生成的结点,它们是cnt都是1,其余的蓝色结点的cnt仍是0。每插入一个元素,仅新增log(n)个结点,耗时log(n),因此建树的时间、空间均为nlog(n)
那么我们要查询区间就是从两个历史版本的root开始相减。就可以像正常的一样查找了。
这个是代码。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=100000;
struct node {
    int tot;
    int ch[2];
};
node tree[MAXN*20];
int ncnt,root[MAXN+5];
int a[MAXN+5],b[MAXN+5];
int n,m,bn;
void init()
{
    ncnt=0;
}
void insert(int &cur,int val,int l,int r)
{
    ncnt++;
    tree[ncnt]=tree[cur];
    cur=ncnt;
    tree[cur].tot++;
    if(l==r)
        return ;
    int mid=(l+r)>>1;
    if(val<=mid)
        insert(tree[cur].ch[0],val,l,mid);
    else
        insert(tree[cur].ch[1],val,mid+1,r);
}
void prepare()
{
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        b[i]=a[i];
    }
    sort(b+1,b+n+1);
    bn=unique(b+1,b+n+1)-b-1;
    root[0]=0;
    for(int i=1;i<=n;i++)
    {
        int val=lower_bound(b+1,b+n+1,a[i])-b;
        root[i]=root[i-1];
        insert(root[i],val,1,bn);
    }
}
int find(int x,int y,int k,int l,int r)
{
    if(l==r)
        return l;
    int mid=(l+r)>>1;
    int t=tree[tree[x].ch[0]].tot-tree[tree[y].ch[0]].tot;
    if(k<=t)
        return find(tree[x].ch[0],tree[y].ch[0],k,l,mid);
    else
        return find(tree[x].ch[1],tree[y].ch[1],k-t,mid+1,r);
}
void solve()
{
    int l,r,k;
    for(int i=1;i<=m;i++)
    {
        scanf("%d %d %d",&l,&r,&k);
        printf("%d\n",b[find(root[r],root[l-1],k,1,bn)]);
    }
}
int main()
{
    init();
    prepare();
    solve();
}

不过这是不带修改的,带修改的由于要影响以前的n个版本,如果暴力更新,时间就会直接炸掉,所以我们用树套树的的办法来解决,即再加一个树状数组来维护序列的前缀和。
但是怎么树套树呢?这里有一个很简单的理解方法.
我们先看一下树状树组 .
这里写图片描述
这个图很简单的显示了对应的数组下标的对应区间,所以我们的第i个历史版本的线段树不再是记录前缀和,而是记录对应的区间(如上图),这样每次更改和查询都是O(logn*logn) 的。
这是带修改的。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=500005,MAXQ=100005;
struct node
{
    int tot;
    int ch[2];
};
node tree[MAXN*20];
int root[MAXN];
int ncnt;
int a[MAXN],b[MAXN*2];
int n,m,bn;
int cntL,cntR,L[MAXN],R[MAXN];
int pos[MAXN];
struct cmd
{
    int l,r,k;
    char op;
}query[MAXQ];
void SegUpdate(int &cur,int l,int r,int pos,int d)
{
    if(!cur)
    {
        ncnt++;
        tree[ncnt]=tree[cur];
        cur=ncnt;
    }
    tree[cur].tot+=d;
    if(l==r)
        return ;
    int mid=(l+r)>>1;
    if(pos<=mid)
        SegUpdate(tree[cur].ch[0],l,mid,pos,d);
    else
        SegUpdate(tree[cur].ch[1],mid+1,r,pos,d);
}
void BitUpdate(int x,int pos,int d)
{
    while(x<=n)
    {
        SegUpdate(root[x],1,bn,pos,d);
        x+=x&-x;
    }
}
int GetPos(int x)
{
    return lower_bound(b+1,b+bn+1,x)-b;
}
void prepare()
{
    char s[20];
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        b[i]=a[i];
    }
    bn=n;
    for(int i=1;i<=m;i++)
    {
        scanf("%s",s);
        query[i].op=s[0];
        if(s[0]=='Q')
            scanf("%d %d %d",&query[i].l,&query[i].r,&query[i].k);
        else
        {
            scanf("%d %d",&query[i].l,&query[i].k);
            b[++bn]=query[i].k;
        }
    }
    sort(b+1,b+bn+1);
    bn=unique(b+1,b+bn+1)-b-1;
    root[0]=0;
    for(int i=1;i<=n;i++)
    {
        pos[i]=GetPos(a[i]);
        BitUpdate(i,pos[i],1);
    }
}
int SegKth(int l,int r,int k)
{
    if(l==r)
        return l;
    int mid=(l+r)>>1;
    int tl=0,tr=0,sum;
    for(int i=1;i<=cntL;i++)
        tl+=tree[tree[L[i]].ch[0]].tot;
    for(int i=1;i<=cntR;i++)
        tr+=tree[tree[R[i]].ch[0]].tot;
    sum=tr-tl;
    if(k<=sum)
    {
        for(int i=1;i<=cntL;i++)
        L[i]=tree[L[i]].ch[0];
        for(int i=1;i<=cntR;i++)
        R[i]=tree[R[i]].ch[0];
        return SegKth(l,mid,k);
    }
    else
    {
        for(int i=1;i<=cntL;i++)
        L[i]=tree[L[i]].ch[1];
        for(int i=1;i<=cntR;i++)
        R[i]=tree[R[i]].ch[1];
        return SegKth(mid+1,r,k-sum);
    }
}
int BitKth(int st,int ed,int k)
{
    cntL=cntR=0;
    while(st>0)
    {
        L[++cntL]=root[st];
        st-=st&-st;
    }
    while(ed>0)
    {
        R[++cntR]=root[ed];
        ed-=ed&-ed;
    }
    int pos=SegKth(1,bn,k);
    return b[pos];
}

void solve(){
    int st=0,ed=0,k=0,ans=0;
    for(int i=1;i<=m;i++)
    {
        if(query[i].op=='Q')
        {
            st=query[i].l;
            ed=query[i].r;
            k=query[i].k;
            ans=BitKth(st-1,ed,k);
            printf("%d\n",ans);
        }
        else
        {
            st=query[i].l,k=query[i].k;
            BitUpdate(st,pos[st],-1);
            pos[st]=GetPos(k);
            BitUpdate(st,pos[st],1);
        }
    }
}
int main()
{
    ncnt=0;
    prepare();
    solve();
}

猜你喜欢

转载自blog.csdn.net/qq_35713030/article/details/78703755