您的位置:首页 > 其它

【poj 2104】K-th Number 主席树入门

2016-10-03 17:11 393 查看
给你一个序列然后m次询问,找给出区间l到r内的第k大的数。

学习主席树的第一道题,看了很多题解才略知皮毛,然后感觉黄学长的代码写的很好(最后我没怎么压行却只用了40行),就借鉴了一下(厚着脸皮说O(∩_∩)O~)。

推荐文库http://wenku.baidu.com/link?url=wJFjRpLWmXrUr03ZoVIeexFYmNIiyM2gA8MweOcbF6DZ4oAyUsv9q6keispR8OS3h7O4gPRfdP3la0dagIkxSpbDbdau74aoznqQuZBAQNm  讲的太详细,其实只要把里面的那几张图,就是手画的那个看懂了基本上就能理解了

自己总结:分析不询问区间的情况,先离散化然后建立一个权值线段树,就可以查询出哪些值出现了多少次,然后树上二分,就可以找到答案。但是问题是现在还有区间的要求,别怕依然可以用这个思想来做。在原来的想法中最重要的一点就是我们能够快速的查询出l->r比mid小的数有多少个(左子树sum),而现在不能直接这样因为有区间范围限制,但是因为没有修改操作,所以满足区间解法即l->mid中比mid小的数等于1->mid - 1->l 那么我们可以把每一个区间建立一个线段树,但是空间复杂度搞到n2,再仔细观察,我们发现其实有很多节点重复,例如1->l与1->l-1其实只有l这一条链不同,那好啊直接用之前的就可以了嘛。(在文库的那篇有图片可以帮组理解)

具体看代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define maxn 100020
using namespace std;
int n,cur[maxn],rt[80*maxn],s[80*maxn],t[maxn],m,tot,cnt,ls[maxn*80],rs[maxn*80];
int find(int x){return lower_bound(t+1,t+1+cnt,x)-t;}

void inster(int l,int r,int x,int& y ,int v){
y=++tot;//因为是应用可以直接建立新的节点
s[y]=s[x]+1;//因为加入了新的数所以一定比之前的节点多一
if(l==r)return;
int mid=l+r>>1;
ls[y]=ls[x],rs[y]=rs[x];//还不知道是向左还是向右 管他三七二十一先赋值再说 反正后面引用会修改
if(v>mid)inster(mid+1,r,rs[x],rs[y],v);//ls[y]不变
else inster(l,mid,ls[x],ls[y],v);//rs不变
}
int query(int l,int r,int x,int y,int c){////树上二分
if(l==r)return r;////找到唯一答案
int mid=l+r>>1;
int k=s[ls[y]]-s[ls[x]];////和一般的线段树一模一样
if(c<=k)return query(l,mid,ls[x],ls[y],c);
else return query(mid+1,r,rs[x],rs[y],c-k);
}

int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",cur+i),t[i]=cur[i];
sort(t+1,t+1+n);
cnt=unique(t+1,t+1+n)-t-1;//离散化
for(int i=1;i<=n;i++)inster(1,cnt,rt[i-1],rt[i],find(cur[i]));//修改建树
int a,b,c;
while(m--){
scanf("%d%d%d",&a,&b,&c);
printf("%d\n",t[query(1,cnt,rt[a-1],rt[b],c)]);//查询答案
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: