主席树或线段树-poj2104K-th Number

http://poj.org/problem?id=2104
我看来这两篇博客
http://blog.csdn.net/a2459956664/article/details/51302474
http://blog.sina.com.cn/s/blog_a46ca3520101be63.html

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

这里写图片描述

然后这个是我的代码:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#define Ll long long 
using namespace std;
struct tree{
    int x,y,l,r;
}T[262144];
int a[100005],q[100000*18];
int n,m,x,y,z,ll;
void maketree(int x,int y,int z){
    T[z].x=x; T[z].y=y;
    if(x==y){
        T[z].l=T[z].r=++ll;
        q[ll]=a[x];
        return;
    }
    maketree(x,(x+y)/2,z*2);
    maketree((x+y)/2+1,y,z*2+1);
    int X=z*2,Y=z*2+1;
    T[z].l=ll+1;
    T[z].r=ll+y-x+1;
    int l=T[X].l,r=T[Y].l;
    while(l<=T[X].r&&r<=T[Y].r)
        if(q[l]>q[r])
            q[++ll]=q[r++];
        else
            q[++ll]=q[l++];
    while(l<=T[X].r)q[++ll]=q[l++];
    while(r<=T[Y].r)q[++ll]=q[r++];
}
int er(int num,int z){
    int l=T[num].l,r=T[num].r,ans=-1;
    while(r>=l){
        int mid=l+r>>1;
        if(q[mid]<=z){
            ans=max(ans,mid);
            l=mid+1;
        }else r=mid-1;
    }
    return max(ans-T[num].l+1,0);
}
int outit(int num,int x,int y,int z){
    if(x<=T[num].x&&T[num].y<=y)
        return er(num,z);
    int ans=0;
    num=num*2;
    if(T[num].y>=x)ans+=outit(num,x,y,z);
    if(T[num+1].x<=y)ans+=outit(num+1,x,y,z);
    return ans;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    maketree(1,n,1);
    while(m--){
        scanf("%d%d%d",&x,&y,&z);
        int l=-1e9,r=1e9;
        while(r>l){
        //  cout<<l<<' '<<r<<endl;
            int mid=l+r>>1;
            if(outit(1,x,y,mid)>=z)r=mid;else l=mid+1;
        }
        printf("%d\n",l);
    }
}

有两点强调一下;
我线段树的题目,关于线段树的数组要开多大?
假如下标为1~n;
设2^k刚好大于或等于n;
那么开成(2^(k+1))大小就好了;
因为线段树就是一个满二叉树;

还有一点我还没研究好;

关于主席树
http://blog.csdn.net/fop_zz/article/details/69240536
代码:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#define For(i,j,k) for(int i=j;i<=k;i++)
using namespace std;
const int N=1e5*18;//n*log2(n)
int L[N],R[N],val[N],root[100001],a[100001],t[100001];
int n,m,tot,len;
inline void update(int aci,int &x,int l,int r,int v){
    x=++tot;
    val[x]=val[aci]+1;
    L[x]=L[aci];
    R[x]=R[aci];
    if(l==r)    return;
    int mid=(l+r)>>1;
    if(v<=mid)  update(L[aci],L[x],l,mid,v);else update(R[aci],R[x],mid+1,r,v);
}
inline int query(int x,int y,int l,int r,int v){
    if(l==r)    return l;
    int now=val[L[y]]-val[L[x]];
    int mid=(l+r)>>1;
    if(now>=v)  return query(L[x],L[y],l,mid,v);else return query(R[x],R[y],mid+1,r,v-now);
}
int main(){
    scanf("%d%d",&n,&m);
    For(i,1,n)  scanf("%d",&a[i]);
    For(i,1,n)  t[i]=a[i];
    sort(t+1,t+n+1);
    len=unique(t+1,t+n+1)-t-1;//判重 
    For(i,1,n)  a[i]=lower_bound(t+1,t+len+1,a[i])-t,update(root[i-1],root[i],1,len,a[i]);
    For(i,1,m){
        int t1,t2,k;
        scanf("%d%d%d",&t1,&t2,&k);
        printf("%d\n",t[query(root[t1-1],root[t2],1,len,k)]);
    }
}
posted @ 2017-04-05 14:44  largecube233  阅读(109)  评论(0编辑  收藏  举报