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

POJ_2104_Kth(主席树)

时间:2016-05-19 20:51:45      阅读:190      评论:0      收藏:0      [点我收藏+]

标签:

描述


http://poj.org/problem?id=2104

给出一个n个数的数列,m次询问,每次询问求区间[l,r]中第k小的数,无修改操作.

 

分析


静态的主席树裸题.

首先考虑把数据离散化,这样一共有n个数,分别为1,2,...,n-1,n(如果没有重复的话)(如果题目里面说有重复且重复数字排名相同,就去一下重就好了).用N=n的线段树来表示某一区间当前的情况,其中节点a[k]表示在这个区间内,属于[a[k].L,a[k].R]的数字共有多少.这样在这个区间上求第K小数的操作就类似于平衡树上的操作,走到一个节点,如果左孩子的数字个数a[a[k].l].s>=k,那么第K大的数就在左孩子区间,否则就在右孩子区间.这样一棵线段树可以表示数列中的一个区间.那要求任意区间的,每一个区间都要有自己的线段树吗?不必.类似求任意区间的和值,可以利用前缀和的思想,每一棵线段树代表数列从第一个数到第i个数的区间,这样只需要n棵线段树即可.而每一棵线段树的形状,大小,含义都是一样的,所以在数列区间[l,r]中属于[a[k].L,a[k].R]的数的个数就是在数列区间[1,r]中的个数减去在数列区间[1,l-1]中的个数,所以用两棵前缀和线段树相减就可以得到在数列区间[l,r]中的个数.

以上就是解决问题的基本思路.

但是n棵线段树就需要n^2级别的空间,会MLE,解决这个问题的就是主席树(可持久化线段树).(其实我不太懂这个名字的含义)

对于表示数列区间[1,i]的线段树,它相对于表示数列区间[1,i-1]的线段树来说,多了一个数字A[i],那就是在A[i]的节点上s值要+1,并且要向上更新,所有包含A[i]的节点都要更新,这样更新的就是从上到下一整条链,而其他的点并没有变,所以没次只用更新logn个点(第一次也是).所以n次建树共需要nlogn个点,空间就够用了(开空间之前动手算一下log).

p.s.

1.我的写法是在进入某一节点之前就修改它相关的值,也可以在函数的参数里使用引用,进入某一节点之后再修改,更简洁,但初学还是如下写法比较好理解.

2.学习了新的离散化写法,也可以用一个结构体把id数组和a数组放在一起,效果是一样的,但这样写更简洁.预处理的复杂度是O(n)的,当然如果有重复的话写成结构体好去重.当然也可以直接排序去重,之后再用O(nlogn)的时间二分查找一遍,这样就不需要id数组了.

技术分享
#include <cstdio>
#include <algorithm>
using namespace std;

const int maxn=100000+5;
int n,m,cnt;
int a[maxn],b[maxn],id[maxn],root[maxn];
struct node{ int l,r,x; }t[maxn*20];
void update(int l,int r,int now,int pre,int x){
    if(l==r) return;
    int mid=l+(r-l)/2;
    if(x<=mid){
        t[now].l=++cnt;
        t[now].r=t[pre].r;
        t[t[now].l].x=t[t[pre].l].x+1;
        update(l,mid,t[now].l,t[pre].l,x);
    }
    else{
        t[now].l=t[pre].l;
        t[now].r=++cnt;
        t[t[now].r].x=t[t[pre].r].x+1;
        update(mid+1,r,t[now].r,t[pre].r,x);
    }
}
int query(int l,int r,int x,int y,int k){
    if(l==r) return l;
    int mid=l+(r-l)/2;
    int s=t[t[y].l].x-t[t[x].l].x;
    if(k<=s) return query(l,mid,t[x].l,t[y].l,k);
    else return query(mid+1,r,t[x].r,t[y].r,k-s);
}
bool cmp(int x,int y){ return a[x]<a[y]; }
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]), id[i]=i;
    sort(id+1,id+n+1,cmp);
    for(int i=1;i<=n;i++) b[id[i]]=i;
    for(int i=1;i<=n;i++){
        root[i]=++cnt;
        update(1,n,cnt,root[i-1],b[i]);
    }
    while(m--){
        int x,y,k;
        scanf("%d%d%d",&x,&y,&k);
        printf("%d\n",a[id[query(1,n,root[x-1],root[y],k)]]);
    }
    return 0;
}
View Code

 

p.s.

1.突然想起来第二种用到二分的离散化方法当初是自己想出来的...虽然不是很好用,但NOIP以前学习和练习的时候一直用的是自己想的东西,lca的最朴素算法也是自己想的,后来发现和书上写得一模一样.现在离thusc和NOI近了,一直在想抓紧时间多学一点,这样的想法是没错的,要抓紧时间,不能太懒散,但是有时候未免太过功利,其实自己是明白自己基本没什么希望的,但是这毕竟是自己热爱的和想要做的事.就算最后什么奖都没有,一次次地打酱油又怎样呢?自己的实力确实差得很远,有梦想是对的,但不该太功利,我应该为自己能够继续追逐梦想而感到幸运.一直以来都不应该那样浮躁,静不下心来.我应该抓紧时间去享受自己的OI,用心去做自己想要做的事,趁自己还有机会.所以很重要的:不能放弃思考.

POJ_2104_Kth(主席树)

标签:

原文地址:http://www.cnblogs.com/Sunnie69/p/5509867.html

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