码迷,mamicode.com
首页 > 其他好文 > 详细

主席树初步

时间:2019-08-14 20:10:41      阅读:89      评论:0      收藏:0      [点我收藏+]

标签:如何   pac   tree   line   动态   ase   ast   getch   next   

前置知识

  1.线段树。。。

  (好像没了

  2.(可知可不知,可能会有帮助)动态开点线段树

 主席树(可持久化线段树)

  一看可持久化,我们总会想到一些恐怖的算法.但是其实理解并不难,而这里我只是将主席树的思想讲清楚(尽量),题还是自己刷(虽然我就没刷几道

  先看一道 模板题

题目描述

如题,给定N个整数构成的序列,将对于指定的闭区间查询其区间内的第K小值。

输入格式

第一行包含两个正整数N、M,分别表示序列的长度和查询的个数。

第二行包含N个整数,表示这个序列各项的数字。

接下来M行每行包含三个整数l,r,k l, r, kl,r,k , 表示查询区间[l,r][l, r][l,r]内的第k小值。

输出格式

输出包含k行,每行1个整数,依次表示每一次查询的结果

  我们发现这道题(其实可以用Splay写很难处理,因为我们没有学过什么数据结构可以维护区间第k大(平衡树的dalao可以忽略

  那么如何处理这个数组是一个关键;

  回顾一下,我们在求前缀和时,如何求一个区间的和?用一个前缀和去减另一个前缀和.

  类比一下,我们是否可以用一个区间减去另一个区间得到第k大?

  是可以的.这里引入平衡树中一个操作,平衡树左子树维护比这个节点值小的子树,右子树维护比这个节点值大的子树;

  平衡树是如何操作的呢?

  保存每个节点的子树大小,比较排名k与左子树的大小,排名k小于左子树大小,那么我们要找的点一定在左子树上,那么走左子树;

  如果排名比左子树大,那么将排名减去左子树大小和自身,走右子树

  技术图片

 

  注意,这个一定要看懂!

  这个概念如果理解了话,那么就简单了.考虑线段树也是一颗二叉树,与平衡树一个性质,并且线段树本质维护的就是区间,那么我们考虑维护一个值域线段树.

  那么就可以初略的定义,主席树是维护一颗值域线段树,将每一个位置作为一个时间点,继承前一个点,并添加自己的值.

  如果要求区间 l 到 r 第k大,那么就可以用 r 时间点的线段树减去 l - 1 时间点的线段树,然后得到这个区间中某个值域中有多少个数,然后用上面所说类似于平衡树的做法,

  (图我是真的画不出来了QAQ)

  为了补偿,在代码中会有详细解释

  Code

  

#include<bits/stdc++.h>
#define maxn 400007
using namespace std;
int n,m,tot,a[maxn],b[maxn];

template<typename type_of_scan>
inline void scan(type_of_scan &x){
    type_of_scan f=1;x=0;char s=getchar();
    while(s<0||s>9){if(s==-)f=-1;s=getchar();}
    while(s>=0&&s<=9){x=x*10+s-0;s=getchar();}
    x*=f;
}
template<typename Tops,typename... tops>
inline void scan(Tops &x,tops&... X){
    scan(x),scan(X...);
}
template<typename type_of_print>
inline void print(type_of_print x){
    if(x<0) putchar(-),x=-x;
    if(x>9) print(x/10);
    putchar(x%10+0);
}

struct node{
    int l,r,sum;
};

struct tree{
    node tr[maxn*20];int t[maxn>>1]; 
    void add(int &rt,int pre,int l,int r,int x){
        rt=++tot;//建一个点保存新的信息 
        tr[rt]=tr[pre],tr[rt].sum++;
        //继承前面节点,并且添加自身 
        if(l==r) return ;
        int mid=l+r>>1;//这里需要注意,l和r是表示值域 
        if(mid>=x) add(tr[rt].l,tr[pre].l,l,mid,x);
        else add(tr[rt].r,tr[pre].r,mid+1,r,x);
        //这里就可以看出主席树的优越性,每一次建logn个点 
    }
    //从此可以看出来,每次加的只有一条从根到叶子的新边(其他边是继承前面的 
    //因此空间复杂度nlogn,注意一下数组 
    int query(int u,int v,int l,int r,int k){
        if(l==r) return l;//返回位置 
        int mid=l+r>>1;
        int x=tr[tr[v].l].sum-tr[tr[u].l].sum;
        //用时间靠后的减去时间靠前的,
        //得到区间信息 
        if(k<=x) return query(tr[u].l,tr[v].l,l,mid,k);
        else return query(tr[u].r,tr[v].r,mid+1,r,k-x);
        //类似于平衡树操作 
    }
}t;

int main(){
    scan(n,m);
    for(int i=1;i<=n;i++)
        scan(a[i]),b[i]=a[i];
    sort(b+1,b+1+n);
    int ol=unique(b+1,b+1+n)-b-1;
    for(int i=1;i<=n;i++){
        a[i]=lower_bound(b+1,b+1+ol,a[i])-b;
        //离散化
        t.add(t.t[i],t.t[i-1],1,ol,a[i]);
        //注意建立新的节点时,要继承前一个时间段的 
    }//不用建树,直接上 
    for(int i=1,l,r,k;i<=m;i++){
        scan(l,r,k);
        int pos=t.query(t.t[l-1],t.t[r],1,ol,k);
        printf("%d\n",b[pos]);
    }
    return 0;
}

 

总结  

  这样大概就讲完了思想,这里介绍一道 水题 ,不过对于加深主席树的思想有重大帮助(将主席树看成一颗前缀线段树)

  我会附上我的代码(因为码风大家都不太相同,如果与我的码风相似了话,看起来就会很享受)

  Code

  

#include<bits/stdc++.h>
#define maxn 100007
using namespace std;
int n,m,fa[maxn],head[maxn],son[maxn],sz[maxn],ol;
int T[maxn],tot,a[maxn],top[maxn],dep[maxn],b[maxn];
int lastans,cent;
struct node{
    int next,to;
}edge[maxn<<1];
struct ML{
    int l,r,sum;
};

template<typename type_of_scan>
inline void scan(type_of_scan &x){
    type_of_scan f=1;x=0;char s=getchar();
    while(s<0||s>9){if(s==-)f=-1;s=getchar();}
    while(s>=0&&s<=9){x=x*10+s-0;s=getchar();}
    x*=f;
}
template<typename Tops,typename... tops>
inline void scan(Tops &x,tops&... X){
    scan(x),scan(X...);
}
template<typename type_of_print>
inline void print(type_of_print x){
    if(x<0) putchar(-),x=-x;
    if(x>9) print(x/10);
    putchar(x%10+0);
}

inline void add(int u,int v){
    edge[++cent]=(node){head[u],v};head[u]=cent;
    edge[++cent]=(node){head[v],u};head[v]=cent;
}

struct tree{
    ML tr[maxn*20];
    void add(int &rt,int pre,int l,int r,int x){
        rt=++tot;
        tr[rt]=tr[pre],tr[rt].sum++;
        if(l==r) return ;
        int mid=l+r>>1;
        if(x<=mid) add(tr[rt].l,tr[pre].l,l,mid,x);
        else add(tr[rt].r,tr[pre].r,mid+1,r,x);
    }
    int query(int u,int v,int lca,int flca,int l,int r,int k){
        if(l==r) return l;
        int mid=l+r>>1;
        int x=tr[tr[u].l].sum+tr[tr[v].l].sum-tr[tr[lca].l].sum-tr[tr[flca].l].sum;
        if(k<=x) return query(tr[u].l,tr[v].l,tr[lca].l,tr[flca].l,l,mid,k);
        else return query(tr[u].r,tr[v].r,tr[lca].r,tr[flca].r,mid+1,r,k-x);
    }
}t;

void dfs1(int x){
    sz[x]=1;t.add(T[x],T[fa[x]],1,ol,a[x]);
    for(int i=head[x];i;i=edge[i].next){
        int y=edge[i].to;
        if(y==fa[x]) continue;
        fa[y]=x;dep[y]=dep[x]+1;
        dfs1(y);sz[x]+=sz[y];
        if(sz[son[x]]<sz[y]) son[x]=y;
    }
}

void dfs2(int x,int tp){
    top[x]=tp;
    if(!son[x]) return ;
    dfs2(son[x],tp);
    for(int i=head[x];i;i=edge[i].next){
        int y=edge[i].to;
        if(y==fa[x]||y==son[x]) continue;
        dfs2(y,y);
    }
}

int get_lca(int x,int y){
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]]) swap(x,y);
        x=fa[top[x]];
    }
    return dep[x]<dep[y]?x:y;
}

int main(){
    scan(n,m);
    for(int i=1;i<=n;i++) scan(a[i]),b[i]=a[i];
    sort(b+1,b+1+n);
    ol=unique(b+1,b+1+n)-b-1;
    for(int i=1;i<=n;i++)
        a[i]=lower_bound(b+1,b+1+ol,a[i])-b;
    for(int i=1,u,v;i<=n-1;i++)
        scan(u,v),add(u,v);
    dfs1(1),dfs2(1,1);
    for(int i=1,u,v,k;i<=m;i++){
        scan(u,v,k);u^=lastans;
        int lca=get_lca(u,v);
        int pos=t.query(T[u],T[v],T[lca],T[fa[lca]],1,ol,k);
        printf("%d\n",b[pos]);lastans=b[pos];
    }
}

如果你看完了这篇博客并想骂博主讲的太简单,说明你已经超越博主了QAQ

 

end 

 

主席树初步

标签:如何   pac   tree   line   动态   ase   ast   getch   next   

原文地址:https://www.cnblogs.com/waterflower/p/11354029.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!