您的位置:首页 > 其它

POJ-2104:K-th Number(主席树)

2017-10-15 19:27 281 查看
题目链接:点击打开链接

题目大意:

给你一串数字,然后查询 l r  区间上第k小的数。

解题思路:

这个就是主席树的模板题了,其实主席树只算辅助作用。本身这道题的解题方法也比较巧妙。

题目会给你一串数,然后求 l r 区间上的第k小的数,

求第k小,这里需要用到一个前缀和的性质,前缀和统计的是区间上出现的数的个数,描述不清楚,举个例子

对于 1,1,2,4,6,7

那么1位置出现2次 2位置出现1次 1~2区间一共就出现了3次 1~5 出现4次 5~10 区间出现2次,那么这个东西有什么用呢,假设我们现在要求这些数中的第3小,那么判断过程就是每次在当前结点判断是否左儿子中数的数量大于当前要求的k 大于的话就往左儿子继续找,否则的话往右儿子找第 k-cnt小即可  cnt为左儿子中数出现的次数,最后递归到最后就可以得到答案了,那么我们如何求任意 l r 区间之间的第k小呢,  假设当前有一个数组 a
  a[k]表示前k个数的在整个区间出现的次数, 那么我们如果求
l r 区间上的第k小时 直接执行 a[r] - a[l-1],然后对得到的新的树按以上的算法求解即可。

那么按照正常的方法,我们应该怎么办呢,对每一个a[i]建立一颗线段树,那空间复杂度就要炸了,那么我们应该怎么办呢,其实对于你新建的线段树来说,其实每次修改操作你只改了一个结点,那么新的线段树跟上一棵线段树比起来也就每层改变了一个结点,其他的结点可以用上一棵线段树的结点 大大的减少了空间复杂度。文字难以描述,上图,



通过这样的建树大大节省了空间要求 也就可以进行上述的操作了,以下贴代码,

#include <cstdio>
#include <vector>
#include <algorithm>
#define rank ra
#define pb push_back
#define lson rt<<1
#define rson rt<<1|1
using namespace std;
typedef long long ll;
const int N=100005;
int n,m,cnt,root
,a
,x,y,k;
vector<int> v;
int getid(int x){ return lower_bound(v.begin(),v.end(),x)-v.begin()+1; }
struct node
{
int l,r,sum;
}T[N*40];
void update(int l,int r,int &x,int y,int pos) //建立新的线段树
{
T[++cnt]=T[y];
T[cnt].sum++;
x=cnt;
if(l==r)
return ;
int mid=(l+r)>>1;
if(mid>=pos) //只新建需要更新的结点即可
update(l,mid,T[x].l,T[y].l,pos);
else
update(mid+1,r,T[x].r,T[y].r,pos);
}
int query(int l,int r,int x,int y,int k)
{
if(l==r)
return l;
int mid=(l+r)>>1;
int sum=T[T[y].l].sum-T[T[x].l].sum; //查询操作
if(sum>=k)
return query(l,mid,T[x].l,T[y].l,k);
else
return query(mid+1,r,T[x].r,T[y].r,k-sum);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
v.pb(a[i
4000
]);
}
sort(v.begin(),v.end()); //排序去重
v.erase(unique(v.begin(),v.end()),v.end());
for(int i=1;i<=n;i++)
update(1,n,root[i],root[i-1],getid(a[i]));
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&x,&y,&k);
printf("%d\n",v[query(1,n,root[x-1],root[y],k)-1]);
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: