POJ 2104 (求区间第K大)

题意:

         给出 N 个数 , M 次查询。每次查询 L R 区间中第 K 大的数。

思路:

线段树+二分区间数列

转自:http://blog.sina.com.cn/s/blog_a46ca3520101be63.html 

题目意思非常很简单。给十万个数,每次询问一段连续区间的第k大值。询问次数达到5000次。
     在线段树中搜索出来的题目,但是很明显,区间需要记录什么信息才可以查询第k大值就很不好想。
     最后想到,只有把这一段中的每个数的大小编号记录才能满足要求,那么就是把这一段中的每个数排序,那么存储的空间就变成了nlogn,构树的时间复杂度也是nlogn,线段树支持一个查询。一段中小于等于tmp的数的个数,用二分来完成这个查询。
     最后找一段中的第k小数,二分枚举答案查询即可。
     复杂度分析,查询的复杂度,一次二分枚举答案,查询时二分区间,找到区间时,二分数列,三次二分,复杂度为m*loginf*log100000*log100000 大概在5000万左右的复杂度,建树的复杂度大概在200万,所以总的时间复杂度在5000万左右。20s的题目,这个复杂度是可以满足的。

注意:

原文作者提供的思路是:在找到区间时,求一段中小于等于tmp的数的个数,是用二分来完成的,所以不会TLE。

#include <iostream>
#include <stdio.h>
#include <string.h>
#define ls t<<1
#define rs t<<1|1
#define midt (tr[t].l+tr[t].r)>>1
using namespace std;
const int maxn=111111;
int a[maxn];
int d[maxn*20],lon;
const int inf=1111111111;
struct
{
    int l,r;
    int ll,rr;
}tr[maxn*4];

int maketree(int t,int l,int r)
{
    tr[t].l=l;
    tr[t].r=r;
    if(l==r)
    {
        d[++lon]=a[l];
        tr[t].ll=lon;
        tr[t].rr=lon;
        return(0);
    }
    int mid=midt;
    maketree(ls,l,mid);
    maketree(rs,mid+1,r);
    tr[t].ll=lon+1;
    int l1=tr[ls].ll,r1=tr[ls].rr;
    int l2=tr[rs].ll,r2=tr[rs].rr;
    while(l1<=r1&&l2<=r2)
    {
        if(d[l1]<d[l2])
        d[++lon]=d[l1++];
        else
        d[++lon]=d[l2++];
    }
    while(l1<=r1)
    d[++lon]=d[l1++];
    while(l2<=r2)
    d[++lon]=d[l2++];
    tr[t].rr=lon;
}

int query(int l,int r,int tmp)
{
    if(tmp>=d[r]) return(r-l+1);
    if(tmp<d[l])  return(0);
    int ll=l,rr=r;
    while(ll<rr)
    {
        int mid=(ll+rr)>>1;
        if(d[mid]>tmp)
        rr=mid;
        else
        ll=mid+1;
    }
    return(ll-l);
}

int query(int t,int l,int r,int tmp)
{

    if(l<=tr[t].l&&r>=tr[t].r)
    {
        return(query(tr[t].ll,tr[t].rr,tmp));
    }
    int mid=midt,ret=0;
    if(l<=mid) ret+=query(ls,l,r,tmp);
    if(mid+1<=r) ret+=query(rs,l,r,tmp);
    return(ret);
}

int main()
{
    int n,k;
    scanf("%d %d",&n,&k);
    for(int i=1;i<=n;i++)
    scanf("%d",&a[i]);
    maketree(1,1,n);
    int l,r,tmp;
    for(int i=1;i<=k;i++)
    {
        int l,r,tmp;
        scanf("%d %d %d",&l,&r,&tmp);
        int sm=-inf,bg=inf;
        while(sm<bg)
        {
            int mid=(sm+bg)>>1;
            int t1=query(1,l,r,mid);
            if(t1<tmp)
            sm=mid+1;
            else
            bg=mid;
        }
        printf("%d\n",sm);
    }
    return 0;
}


在线主席树:

#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
#include <cstring>
using namespace std;
#define ls l,mid
#define rs mid+1,r
#define mi (l+r)/2
const int MAXN=1e5+7;
vector <int> a;
typedef struct Node{
    int val;
    int son[2];
}Node;
Node tree[MAXN*20];
int b[MAXN];
int cnt,pos,rtid[MAXN],ans;
int new_node(){
    int rt=++cnt;
    tree[rt].val=0;
    tree[rt].son[0]=tree[rt].son[1]=0;
    return rt;
}
int build(int l,int r){
    int rt=new_node();
    if(l==r){
        return rt;
    }
    int mid=mi;
    tree[rt].son[0]=build(ls);
    tree[rt].son[1]=build(rs);
    return rt;
}
void update(int l,int r,int rt,int lart){
    tree[rt].val=tree[lart].val;
    tree[rt].val++;
    if(l==r) return ;
    int mid=mi;
    if(pos>mid){
        tree[rt].son[0]=tree[lart].son[0];
        tree[rt].son[1]=new_node();
        update(mid+1,r,tree[rt].son[1],tree[lart].son[1]);
    }else{
        tree[rt].son[1]=tree[lart].son[1];
        tree[rt].son[0]=new_node();
        update(l,mid,tree[rt].son[0],tree[lart].son[0]);
    }
}
int query(int l,int r,int rt,int lart,int k){
    if(l==r) return l;
    int mid=mi;
    int sum=tree[tree[rt].son[0]].val-tree[tree[lart].son[0]].val;
    if(sum>=k) return query(ls,tree[rt].son[0],tree[lart].son[0],k);
    else return query(rs,tree[rt].son[1],tree[lart].son[1],k-sum);
}
void ini(){
    a.clear();cnt=0;
}
int main()
{

    int n,m,x,st,en,k,T;
    while(scanf("%d%d",&n,&m)!=-1){
        ini();
        for(int i=1;i<=n;i++) scanf("%d",&b[i]),a.push_back(b[i]);
        sort(a.begin(),a.end());a.erase(unique(a.begin(),a.end()),a.end());
        int len=a.size();
        rtid[0]=build(1,len);
        for(int i=1;i<=n;i++){
            pos=lower_bound(a.begin(),a.end(),b[i])-a.begin()+1;
            rtid[i]=++cnt;
            update(1,len,rtid[i],rtid[i-1]);
        }
        for(int i=1;i<=m;i++){
            scanf("%d%d%d",&st,&en,&k);
            ans=query(1,len,rtid[en],rtid[st-1],k);
            printf("%d\n",a[ans-1]);
        }
    }
}




Splay:


并归树:

               由线段树改编

               利用STL给出的 merge 每次并归即可 时间 O(nlognlognlogn)空间 O(nlogn)

#include <iostream>
#include <vector>
#include <cstdio>
#include <algorithm>
using namespace std;
#define ls l,mid,rt*2
#define rs mid+1,r,rt*2+1
#define sf l,r,rt
#define mi (l+r)/2
const int MAXN=1e5+100;
const int INF=0x3f3f3f3f;
vector <int> tree[MAXN*4],v;
int x,n,m,ppp;
int a[MAXN];
int st,en,k;
int getid(int x){return lower_bound(v.begin(),v.end(),x)-v.begin()+1;}//................多加了一步离散化,实际上不需要。
void push_up(int l,int r,int rt){
    tree[rt].resize(r-l+1);
    merge(tree[rt*2].begin(),tree[rt*2].end(),tree[rt*2+1].begin(),tree[rt*2+1].end(),tree[rt].begin());//.....STL
}
void build(int l,int r,int rt){
    if(l==r){tree[rt].clear();tree[rt].push_back(getid(a[++ppp]));return ;}
    int mid=mi;
    build(ls);
    build(rs);
    push_up(sf);
    return ;
}
int queryK(int l,int r,int rt,int v){
    if(r<st||l>en) return 0;
    if(st<=l&&r<=en){
       return upper_bound(tree[rt].begin(),tree[rt].end(),v)-tree[rt].begin();
    }
    int mid=mi;
    int ans=0;
    ans+=queryK(ls,v);
    ans+=queryK(rs,v);
    return ans;
}
void solve(){//....................二分枚举,虽然会枚举到区间中不存在的数,但枚举结束后一定会枚举到正确的数
    int l=0;
    int r=n;
    int mid;
    while(l<r){
        mid=mi;
        if(queryK(1,n,1,mid)>=k){
            r=mid;
        }else{
            l=mid+1;
        }
    }
    printf("%d\n",v[l-1]);
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;i++)scanf("%d",&a[i]),v.push_back(a[i]);
    sort(v.begin(),v.end()),v.erase(unique(v.begin(),v.end()),v.end());
    ppp=-1;build(1,n,1);
    while(m--){
        scanf("%d%d%d",&st,&en,&k);
        solve();
    }
}


划分树:





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值