POJ2104 K-th Number

题目大意:给一个大小为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

 

当然这题也能用整体二分做,当然因为数字可能超过1e9,所以还需要一下离散化...

然后离散化完了,就很简单了...

和这题做法一样

http://www.cnblogs.com/Robert-Yuan/p/5103863.html

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

using namespace std;

const int maxn=100010;
const int INF=2*1e9;

struct Query{
    int x,y,k,id,cur;
    bool tp;
}q[maxn<<1],q1[maxn<<1],q2[maxn<<1];

int n,m,tot;
int Hash[maxn],sz,que[maxn];
int ans[maxn],a[maxn],b[maxn],tmp[maxn<<1];

int Find_h(int x){
    int l=0,r=sz+1,mid;
    while(l<r){
        mid=(l+r)>>1;
        if(Hash[mid]>x) r=mid;
        else if(Hash[mid]<x) l=mid;
        else return mid;
    }
    return l;
}

void add(int x,int d){
    for(;x<=sz;x+=x&-x) b[x]+=d;
}

int ask(int x){
    int sum=0;
    for(int i=x;i;i-=i&-i) sum+=b[i];
    return sum;
}

void div(int H,int T,int l,int r){
    if(H>T) return ;
    if(l==r){
        for(int i=H;i<=T;i++)
            if(!q[i].tp) ans[q[i].id]=l;
        return ;
    }
    int mid=(l+r)>>1;
    for(int i=H;i<=T;i++){
        if(q[i].tp && q[i].y<=mid) add(q[i].x,1);
        else if(!q[i].tp) 
            tmp[i]=ask(q[i].y)-ask(q[i].x-1);
    }
    for(int i=H;i<=T;i++)
        if(q[i].tp && q[i].y<=mid) add(q[i].x,-1);
    int l1=0,l2=0;
    for(int i=H;i<=T;i++){
        if(q[i].tp){
            if(q[i].y<=mid) q1[++l1]=q[i];
            else q2[++l2]=q[i];
        }
        else{
            if(q[i].cur+tmp[i]>=q[i].k) q1[++l1]=q[i];
            else
                q[i].cur+=tmp[i],q2[++l2]=q[i];
        }
    }
    for(int i=1;i<=l1;i++) q[H+i-1]=q1[i];
    for(int i=1;i<=l2;i++) q[H+l1+i-1]=q2[i];
    div(H,H+l1-1,l,mid);
    div(H+l1,T,mid+1,r);
}

int main(){
#ifndef ONLINE_JUDGE
    freopen("2104.in","r",stdin);
    freopen("2104.out","w",stdout);
#endif
    
    int l,r,k;

    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);que[i]=a[i];
        q[++tot].tp=1;q[tot].x=i;
    }
    sort(que+1,que+n+1);
    Hash[++sz]=que[1];
    for(int i=2;i<=n;i++){
        while(que[i]==que[i-1]) i++;
        Hash[++sz]=que[i];
    }
    
    for(int i=1;i<=n;i++) q[i].y=Find_h(a[i]);
    
    for(int i=1;i<=m;i++){
        tot++;
        scanf("%d%d%d",&q[tot].x,&q[tot].y,&q[tot].k);
        q[tot].id=i;
    }
    div(1,tot,1,sz);
    for(int i=1;i<=m;i++)
        printf("%d\n",Hash[ans[i]]);

    return 0;
}
View Code

 

posted @ 2016-01-05 14:52  诚叙  阅读(383)  评论(0编辑  收藏  举报