zoukankan      html  css  js  c++  java
  • 主席树学习笔记1:静态主席树

    Part I 静态主席树

    定义

    主席树最基础可以维护区间K大的问题,由于其本质是可持久化线段树,所以要对线段树有很深的理解。

    栗子:区间第K小

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

    首先这种处理区间的问题肯定要想到区间数据结构。显然如果是指定了区间,可以把读入的数据离散化,然后建一颗值域线段树。

    但是要在任意的[l,r]中查询第k小,一些大神就想到了前缀和。

    首先建N颗线段树,第i棵维护区间a1-ai的每个数的出现个数。

    此时值域线段树的结构都要保证完全相等,这样这些线段树就具有了可减性,就可以用前缀和维护了。

    那这样就要建N棵线段树,空间无法承受。

    我们可以轻易发现,维护a1-ai和a1-ai+1的线段树,的每一个非叶节点的子树有一半的结构都是相等的,如果能够只修改一半,空间的问题就会解决。

    那如何处理空间了

    比如说对于一个数1926,817,1989,604,首先离散化

    于是 1926 就等价于3,以此类推。

    那按照刚才的思路,我们可以先建一棵树维护a1-a1

    如下图

     圆圈中的数字代表线段树维护的东西,也就是在这个区间内的数有多少个。

    这个时候我们考虑建a1-a2的线段树,如果重新建这棵树会变成这样

     

    对比前一棵树,我们发现几乎一半的结构都是相同的。(圈出来的即为相同)

    那我们每次先新建一个根节点:

     其中维护a1-ai的根节点为rooti,如下图

    我们每一棵节点都要记录他左右儿子的编号(后面会解释)。

    这样我们可以用让root2的右儿子的编号等于root1右儿子的编号相同的方式使root2的右儿子等于root1的左儿子且节约空间

    个人感觉有点像链表:

    这样要修改的节点就大大减少了。

    至于为什么要记录每个节点的左右儿子编号,正是因为主席树有一半的结构来源与前一棵树,这是这个节点与先前那个节点不构成线段树中i*2和i*2+1的关系

    对于查询,我们要利用到前缀和:

     对于查询,首先对于这棵子树查询K大:

    我们只需要关注这棵子树的左子树出现的数的个数总和与K的大小关系:

    如果小的话:在左子树查K小

    如果大的话:往右子树查(k-左子树总和)小

    那怎么知道L-R的个数:

    前缀和,首先结构相同自然可以直接减啦!

    至此静态主席树求静态第K小就搞定了,下面结合代码(非常丑陋):

    #include<bits/stdc++.h>
    
    using namespace std;
    
    const int MAXN = 2 * 100000 + 10;
    
    inline int read()
    {
        int f = 1 ,x = 0;
        char ch;
        do
        {
            ch =getchar();
            if(ch=='-') f = -1;
        }while(ch<'0'||ch>'9');
        do
        {
            x=(x<<3)+(x<<1)+ch-'0';
            ch = getchar();
        }while(ch>='0'&&ch<='9');
        return f*x;
    }
    
    int n,m;
    
    struct node
    {
        int val;
        int id;
        friend bool operator < (node a1,node a2)
        {
            return a1.val<a2.val;    
        } 
    };
    
    node a[MAXN];
    
    int c[MAXN];
    
    struct Tree
    {
        int lc;
        int rc;
        int sum;
        Tree()
        {
            sum = 0;    
        } 
    };
    
    Tree tree[MAXN*20];
    
    int root[MAXN];
    
    int cnt =0;
    
    inline void init()
    {
        root[0]=0;
        tree[0].lc=tree[0].rc=0;
        tree[0].sum=0;
        return;
    }
    
    inline void update(int num,int &rt ,int l,int r)
    {
        tree[++cnt]=tree[rt];
        rt = cnt;
        tree[rt].sum++;
        if(l==r) return;
        int mid = (l+r)>>1;
        if(num<=mid) update(num,tree[rt].lc,l,mid);
        else update(num,tree[rt].rc,mid+1,r);
    }
    
    inline int query(int l,int r,int k,int x,int y)
    {
        int now = tree[tree[r].lc].sum-tree[tree[l].lc].sum;
        if(x==y) return x;
        else
        {
            int mid = (x+y)>>1;
            if(now>=k)
            {
                return query(tree[l].lc,tree[r].lc,k,x,mid);    
            } 
            else return query(tree[l].rc,tree[r].rc,k-now,mid+1,y);
        } 
    }
    
    int main()
    {
        n = read();
        m = read();
        
        for(int i=1;i<=n;i++) a[i].val=read(),a[i].id = i;
        
        sort(a+1,a+n+1);
        
        for(int i=1;i<=n;i++)
        {
            c[a[i].id]=i;
        }
        
        init();
        
        for(int i=1;i<=n;i++)
        {
            root[i]=root[i-1];
            update(c[i],root[i],1,n);
        }
        
        for(int i=1;i<=m;i++)
        {
            int L =read();
            int R = read();
            int k=read();
            printf("%d
    ",a[query(root[L-1],root[R],k,1,n)].val);
        }
    }

    栗子:可持久化数组

    你需要维护这样的一个长度为 N N 的数组,支持如下几种操作
    
    1.在某个历史版本上修改某一个位置上的值
    
    2.访问某个历史版本上的某一位置的值

    可持久化数组可以用可持久化线段树实现。

    其实就是每次在对应的历史版本把节点复制,然后修改。

    丑陋的代码:

    #include<bits/stdc++.h>
    
    using namespace std;
    
    const int MAXN = 1000001;
    
    inline int read()
    {
        char ch;
        int fl=1;
        int x=0;
        do{
          ch= getchar();
          if(ch=='-')
            fl=-1;
        }while(ch<'0'||ch>'9');
        do{
            x=(x<<3)+(x<<1)+ch-'0';
            ch=getchar();
        }while(ch>='0'&&ch<='9');
        return x*fl;
    }
    
    int n,m;
    
    int a[MAXN];
    
    struct node
    {
        int lc;
        int rc;
        int sum;
    };
    
    node tree[MAXN*20];
    
    int root[MAXN];
    
    int cnt=0;
    
    int tt = 0;
    
    int cc[MAXN],ccnt=0;
    
    inline void build(int& rt,int l,int r)
    {
        ++cnt;
        rt = cnt;
        if(l==r) {
            tree[rt].sum = a[l];
            return;        
        }
        int  mid = (l+r)>>1;
        build(tree[rt].lc,l,mid);
        build(tree[rt].rc,mid+1,r);
    }
    
    inline void update(int& rt,int l,int r,int x,int y)
    {
         ++cnt;
         tree[cnt] = tree[rt];
         rt = cnt;
        if(l==r)
        {
            tree[rt].sum = y;
            return ;
        }
        int mid = (l+r)>>1;
        if(x<=mid)
        {
            update(tree[rt].lc,l,mid,x,y);
        }
        else
        {
            update(tree[rt].rc,mid+1,r,x,y);
        }
    }
    
    inline int query(int rt,int l,int r,int x)
    {
        if(l==r)
        {
            return tree[rt].sum;
        }
        else
        {
            int mid = (l+r)>>1;
            if(x<=mid)
            {
                return query(tree[rt].lc,l,mid,x);
            }
            else
            {
                 return query(tree[rt].rc,mid+1,r,x);
            }
        }
    }
    
    int main()
    {
        n = read(),m = read();
        for(int i=1;i<=n;i++) a[i]=read();
        build(root[0],1,n);
        cc[0]=root[0];
        for(int i=1;i<=m;i++)
        {
            int bb = read();
            int opt = read();
            if(opt==1)
            {
                int x = read();
                int y = read();
                ++tt;
                cc[tt]=cc[bb];
                root[tt]=cc[tt];
                update(root[tt],1,n,x,y);
                cc[tt]=root[tt];
            }
            if(opt==2)
            {
                ++tt;
                cc[tt]=cc[bb];
                root[tt]=cc[tt];
                int x = read();
                printf("%d
    ",query(cc[tt],1,n,x));
            }
        }
    }
  • 相关阅读:
    NUMBER BASE CONVERSION(进制转换)
    2776 寻找代表元(匈牙利算法)
    最长严格上子序列(二分优化)
    c++ bitset类的使用和简介
    笨小猴 2008年NOIP全国联赛提高组
    三国游戏 2010年NOIP全国联赛普及组
    全国信息学奥林匹克联赛(NOIP2014)复赛 模拟题Day2 长乐一中
    codevs 1704 卡片游戏
    热浪
    全国信息学奥林匹克联赛 ( NOIP2014) 复赛 模拟题 Day1 长乐一中
  • 原文地址:https://www.cnblogs.com/wlzs1432/p/9019684.html
Copyright © 2011-2022 走看看