题目大意:给一个大小为n(1e6)的序列,求区间[L,R]上的排行第k的数(5000次)。

AC通道:http://poj.org/problem?id=2104

 

方法一:按数值大小直接排序,记下排行第i的原来的位置是id[i];

排好序之后每次询问从左往右数,如果这个落在[L,R]则k--,k=0时输出...

但这莫不是玄学算法...因为最差复杂度明显是过不了的。

 

于是这就需要将前缀和思想和线段树处理结合的思想了:

传统题目:给一个序列,询问全局排行第k的元素怎么求?

1.nlogn快排预处理,每次O(1)询问

2.二叉排序树,递归下去如果左边子树个数小于等于k往左边走,否则减去之后往右边走[平衡树中的find_kth()]

硬是要你线段树做怎么办?

3.和平衡树类似的思想,将线段树当做一个桶状的结构,记录这个区间内的元素个数,如果左区间的个数小于等于k往左走,否则往右走。

 

现在我们询问的是区间[L,R]第k大,再看看上面的线段树求全局的过程,如果我们照样是将线段树当桶用,现在我们希望能随时知道在某个数值区间内[L,R]内的数有多少怎么办?

前缀和!

将统计至第L-1个元素和第R个元素的两棵线段树调出来,设为x,y,那么当前数值区间内处于[L,R]内的元素个数为s[y].sz-s[x].sz,然后步骤和上面也就一样了。

所以我们理想的就是对每前i个建立一颗桶状的线段树,查询的时候就方便了。

问题又来了:怎么建n棵线段树而不会MLE呢?

注意到每次都只是在上一次的基础上往桶中放一个元素,所以有很多节点都不会变,只有从根到最后放下的位置的这一条logn的链上会有变化,所以我们可以充分利用之前的信息。

先完全复制原来的点,加入这点之后更改当前点信息,然后若是要往左边走,再递归下去修改左边节点的信息就好。

百度文库里有个ppt有图有真相,可以看一看:

http://wenku.baidu.com/link?url=nvjIstm4wwoN6Ef3LZiJhQb6q_mV8irk0vrjue7j1IFZPr0j6k6YCmxtAB7avPSiFfINV59HBTkEdt_NRUKyMxVrkuNaESBJ1QIoTwO1b3S###

 

最后附上代码:

#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

const int maxn=100010;

struct Node{
    int l,r,sz;
}s[maxn*18];

int n,m,cnt,key;
int a[maxn];
int rk[maxn],id[maxn];
int rt[maxn];

bool cmp(const int &x,const int &y){
    return a[x]<a[y];
}

void build(int l,int r,int &pos/*这个别名使用比较巧妙,细细体会*/){
    s[++cnt]=s[pos],pos=cnt,s[cnt].sz++;//++cnt表示这是新建的一个节点,但是传下来的时候,pos保存的是上一棵树的编号,s[pos]是上一棵树在这个位置的节点信息,cnt将在自己的树中将原来的节点取代。
    if(l==r) return ;
    int mid=(l+r)>>1;
    if(key<=mid) build(l,mid,s[cnt].l);//如果是分到左子树,那么右子树可以直接利用上棵树的信息,不用修改了
    else build(mid+1,r,s[cnt].r);
}

int query(int l,int r,int x,int y,int k){
    if(l==r) return l;
    int mid=(l+r)>>1,sz=s[s[y].l].sz-s[s[x].l].sz;//sz表示数值在[l,r]内,位置在x,y子树之间的元素个数
    if(k<=sz) return query(l,mid,s[x].l,s[y].l,k);
    else return query(mid+1,r,s[x].r,s[y].r,k-sz);
}

int main(){
#ifndef ONLINE_JUDGE
    freopen("2104.in","r",stdin);
    freopen("2104.out","w",stdout);
#endif
    int k,l,r;
    
    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++) rk[id[i]]=i;//离散化的操作,每个人的rank排名就是它们新的值
    
    for(int i=1;i<=n;i++){
        rt[i]=rt[i-1];//每次要利用上次的树,先将根节点设成上次的根节点
        key=rk[i],build(1,n,rt[i]);
    }
    
    while(m--){
        scanf("%d%d%d",&l,&r,&k);
        printf("%d\n",a[id[query(1,n,rt[l-1],rt[r],k)]]);
    }
    
    return 0;
}
View Code

相关文章: