主席树

可持久化线段树,也叫作函数式线段树,也就是主席树,(。。。因为先驱就是fotile主席。。Orz。。。)

这里,我将从查找区间第k小值(不带修改)题的可持久化线段树做法中,讲一讲主席树。

可持久化数据结构(Persistent data structure)就是利用函数式编程的思想使其支持询问历史版本、同时充分利用它们之间的共同数据来减少时间和空间消耗。/*找不到比较科学的定义,就拿这个凑凑数吧~~~*/
这个数据结构很坑啊,我研究了一整天才差不多理解了一些。所以,要理解好每一个域或变量的意义。
开讲!
既然叫“函数式线段树”,那么就应该有跟普通线段树相同的地方。一颗线段树,只能维护一段区间里的元素。但是,每个询问的区间都不一样,若是对每段区间都单独建立的线段树,那~萎定了~。因此,就要想,如何在少建,或建得快的情况下,能利用一些方法,得出某个区间里的情况。
比如一棵线段树,记为tree[i][j],表示区间[i,j]的线段树。那么,要得到它的情况,可以利用另外两棵树,tree[1][i-1]和tree[1][j],得出来。也就是说,可以由建树的一系列历史版本推出。
那么,怎么创建这些树呢?
首先,离散化数据。因为如果数据太大的话,线段树会爆~~
在所有树中,是按照当前区间元素的离散值(也就是用大小排序)储存的,在每个节点,存的是这个区间每个元素出现的次数之和(data域)。出现的次数,也就是存了多少数进来(建树时,是一个数一个数地存进来的)。
先建棵线段树,所有的节点data域为0。再一个节点一个节点地添加。把每个数按照自己的离散值,放到树中合适的位置,然后data域+1,回溯的时候也要+1。当然,不能放到那棵空树中,要重新建树。第i棵树存的是区间(原序列)[1,i]。但是,如果是这样,那么会MLE+TLE。因此,要充分利用历史版本。用两个指针,分指当前空树和前一棵树。因为每棵树的结构是一样的,只是里面的data域不同,但是两棵相邻的树,只有一数只差,因此,如果元素要进左子树的话,右子树就会跟上个树这个区间的右子树是完全一样的,因此,可以直接将本树本节点的右子树指针接到上棵树当前节点的右儿子,这样即省时间,又省空间。
每添加一个节点(也就是新建一棵树)的复杂度是O(logn),因此,这一步的复杂度是O(nlogn)。
建完之后,要怎么查找呢?
跟一般的,在整棵树中找第k个数是一样的。如果一个节点的左权值(左子树上点的数量之和)大于k,那么就到左子树查找,否则到右子树查找。其实主席树是一样的。对于任意两棵树(分别存区间[1,i]和区间[1,j] i<j),在同一节点上(两节点所表示的区间相同),data域之差表示的是,原序列区间[i,j]在当前节点所表示的区间里,出现多少次(有多少数的大小是在这个区间里的)。同理,对于同一节点,如果在两棵树中,它们的左权值之差大于等于k,那么要求的数就在左孩子,否则在右孩子。当定位到叶子节点时,就可以输出了。

常数优化的技巧
一种在常数上减小内存消耗的方法:插入值时候先不要一次新建到底,能留住就留住,等到需要访问子节点时候再建下去。这样理论内存复杂度依然是O(Nlg^2N),但因为实际上很多结点在查询时候根本没用到,所以内存能少用一些。
 
动态第K小值
每一棵线段树是维护每一个序列前缀的值在任意区间的个数,如果还是按照静态的来做的话,那么每一次修改都要遍历O(n)棵树,时间就是O(2*M*nlogn)->TLE。考虑到前缀和,我们通过树状数组来优化,即树状数组套主席树,每个节点都对应一棵主席树,那么修改操作就只要修改logn棵树,O(nlognlogn+Mlognlogn)时间是可以的,但是直接建树要nlogn*logn(10^7)会MLE。我们发现对于静态的建树我们只要nlogn个节点就可以了,而且对于修改操作,只是修改M次,每次改变俩个值(减去原先的,加上现在的)也就是说如果把所有初值都插入到树状数组里是不值得的,所以我们分两部分来做,所有初值按照静态来建,内存O(nlogn),而修改部分保存在树状数组中,每次修改logn棵树,每次插入增加logn个节点O(M*logn*logn+nlogn)。

—————————————————————————————————————————————————————————————————————————————

鄙人的一些理解:所谓主席树呢,就是对原来的数列[1..n]的每一个前缀[1..i](1≤i≤n)建立一棵线段树,线段树的每一个节点存某个前缀[1..i]中属于区间[L..R]的数一共有多少个(比如根节点是[1..n],一共i个数,sum[root] = i;根节点的左儿子是[1..(L+R)/2],若不大于(L+R)/2的数有x个,那么sum[root.left] = x)。若要查找[i..j]中第k大数时,设某结点x,那么x.sum[j] - x.sum[i - 1]就是[i..j]中在结点x内的数字总数。而对每一个前缀都建一棵树,会MLE,观察到每个[1..i]和[1..i-1]只有一条路是不一样的,那么其他的结点只要用回前一棵树的结点即可,时空复杂度为O(nlogn)。


K-th Number
Time Limit: 20000MS Memory Limit: 65536K
Total Submissions: 36853 Accepted: 11837
Case Time Limit: 2000MS

Description

You are working for Macrohard company in data structures department. After failing your previous task about key insertion you were asked to write a new data structure that would be able to return quickly k-th order statistics in the array segment. 
That is, given an array a[1...n] of different integer numbers, your program must answer a series of questions Q(i, j, k) in the form: "What would be the k-th number in a[i...j] segment, if this segment was sorted?" 
For example, consider the array a = (1, 5, 2, 6, 3, 7, 4). Let the question be Q(2, 5, 3). The segment a[2...5] is (5, 2, 6, 3). If we sort this segment, we get (2, 3, 5, 6), the third number is 5, and therefore the answer to the question is 5.

Input

The first line of the input file contains n --- the size of the array, and m --- the number of questions to answer (1 <= n <= 100 000, 1 <= m <= 5 000). 
The second line contains n different integer numbers not exceeding 10 9 by their absolute values --- the array for which the answers should be given. 
The following m lines contain question descriptions, each description consists of three numbers: i, j, and k (1 <= i <= j <= n, 1 <= k <= j - i + 1) and represents the question Q(i, j, k).

Output

For each question output the answer to it --- the k-th number in sorted a[i...j] segment.

Sample Input

7 3
1 5 2 6 3 7 4
2 5 3
4 4 1
1 7 3

Sample Output

5
6
3

Hint

This problem has huge input,so please use c-style input(scanf,printf),or you may got time limit exceed.

Source

Northeastern Europe 2004, Northern Subregion
在空树的基础上建立其他树
#include<stdio.h>
#include<algorithm>
#define N  100005
using namespace std;
struct node
{
    int x,y,sum;
}a[N*20];
struct pp
{
    int x,id;
}b[N];
int num,root[N],rank[N];
bool cmp(pp  a,pp  b)
{
    return a.x<b.x;
}
void update(int k,int &c,int x,int y)
{
    a[num++]=a[c];  c=num-1;
    ++a[c].sum;
    if(x==y)  return;
    int mid=(x+y)>>1;
    if(k<=mid)
        update(k,a[c].x,x,mid);
    else
        update(k,a[c].y,mid+1,y);
}
int query(int i,int j,int k,int x,int y)
{
    if(x==y)  return x;
    int t=a[a[j].x].sum-a[a[i].x].sum;
    int mid=(x+y)>>1;
    if(k<=t)
       return  query(a[i].x,a[j].x,k,x,mid);
    else
        return query(a[i].y,a[j].y,k-t,mid+1,y);
}
int main()
{
    int n,m,i,j,k;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        for(i=1;i<=n;i++)
        {
            scanf("%d",&b[i].x);
            b[i].id=i;
        }
        sort(b+1,b+n+1,cmp);
        for(i=1;i<=n;i++)
            rank[b[i].id]=i;
        a[0].x=a[0].y=a[0].sum=0;  root[0]=0;  num=1;
        for(i=1;i<=n;i++)
        {
            root[i]=root[i-1];
            update(rank[i],root[i],1,n);
           // printf("%d\n",root[i]);
        }
        while(m--)
        {
            scanf("%d%d%d",&i,&j,&k);
            printf("%d\n",b[query(root[i-1],root[j],k,1,n)].x);
        }
    }
    return 0;
}

在整棵树的基础上建立其他树



#include<stdio.h>
#include<algorithm>
#define N 100005
#define M N*20
using namespace std;
int num,n,sum[M],lson[M],rson[M],t[N],rank[N];
struct node
{
    int x,id;
}a[N];
bool cmp(node a,node b)
{
    return a.x<b.x;
}
int build(int x,int y)
{
   int root=num++;
    sum[root]=0;
    if(x<y)
    {
        int mid=(x+y)>>1;
        lson[root]=build(x,mid);
        rson[root]=build(mid+1,y);
    }
    return root;
}
int update(int root ,int p,int val)
{
    int newroot=num++, tmp=newroot;
    sum[newroot]=sum[root]+val;
    int l=1, r=n;
    while(l<r)
    {
        int mid=(l+r)>>1;
        if(p<=mid)
        {
            lson[newroot]=num++;  rson[newroot]=rson[root];
            newroot=lson[newroot]; root=lson[root];
            r=mid;
        }
        else
        {
            rson[newroot]=num++;  lson[newroot]=lson[root];
            newroot=rson[newroot];   root=rson[root];
            l=mid+1;
        }
        sum[newroot]=sum[root]+val;
    }
    return tmp;
}
int query(int x,int y,int k)
{
    int l=1,r=n;
    while(l<r)
    {
        int mid=(l+r)>>1;
        int m=sum[lson[y]]-sum[lson[x]];
        if(m>=k)
        {
            r=mid;
            x=lson[x];
            y=lson[y];
        }
        else
        {
            k-=m;
            l=mid+1;
            x=rson[x];
            y=rson[y];
        }
    }
    return l;
}
int main()
{
    int m,i,j,k;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        for(i=1;i<=n;i++)
        {
            scanf("%d",&a[i].x);
            a[i].id=i;
        }
        sort(a+1,a+n+1,cmp);
        for(i=1;i<=n;i++)
            rank[a[i].id]=i;
        num=0;
        t[0]=build(1,n);
        for(i=1;i<=n;i++)
        {
            t[i]=update(t[i-1],rank[i],1);
        }
        while(m--)
        {
            scanf("%d%d%d",&i,&j,&k);
            printf("%d\n",a[query(t[i-1],t[j],k)].x);
        }
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值