• 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
    ",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
    ",Hash[ans[i]]);
    
        return 0;
    }
    View Code
  • 相关阅读:
    AC自动机+全概率+记忆化DP UVA 11468 Substring
    java POI技术之导出数据优化(15万条数据1分多钟)
    验证IP端与数据库Ip端是否重复!!!
    JAVA中IP和整数相互转化(含有掩码的计算)
    Nginx搭建反向代理服务器过程详解
    session原理及实现共享
    Linux部署多个tomcat
    linux下怎么修改mysql的字符集编码
    linux yum 安装mysql
    VM虚拟机下的Linux不能上网
  • 原文地址:https://www.cnblogs.com/Robert-Yuan/p/5102318.html
Copyright © 2020-2023  润新知