题目来源:http://poj.org/problem?id=2104
★通过这个题来简述一下划分树的算法,算是模板题了~
题意:
给一个长度为n的序列,然后有m次询问,每次给一个区间 输出这个区间第k小的数
思路:
具体思路参考博客:https://blog.csdn.net/zxy_snow/article/details/6681086
我就说一些没讲到的细节
划分树的结构以及建树:
划分树 结点是原序列,然后左右子结点是 以 排序后的原序列(数组f)的对应中间值f [ mid ]为基准,小于等于它的按原序列的次序排在左边(排满mid-l+1个为止,就是说有部分右结点会存在等于中间值的数) 其它排在右边 ~ 然后子节点再往下重复操作递归
例如 序列 (1 5 2 3 6 4 7 3 0 0 ) 中间值为 排序后的数组 (0 0 1 2 3 3 4 5 6 ) 中间值为3
故左结点 ( 1 2 3 0 0 )右结点(5 6 4 7 3)
// build代码中 res加加减减就是为了防止重复过多的数进入左结点
查询:
num[ dep ] [ j ] 的意思就是 第dep层前j个有多少个属于左结点,然后如果查找(l,r)第k大的数
我们可以先求出这个区间有多少小于中间值的数cnt int cnt=num[dep][y]-num[dep][x-1];
如果 cnt>=k 那说明我们要求的第k小肯定在这个左区间(l,mid) 里面,不过 明显可以缩小查询的区间
如果 cnt<k 那说明这cnt个的数中不可能有我们要找的第k小,肯定要去 右区间(mid+1,r) 中找,而这时找的是 右区间第k-cnt小 的(这可以理解叭)
缩小区间:
不难发现,假设现在的父结点的总区间为(l,r),而我们的查询区间为(x,y)
父结点的区间可以被分为3块 左(l,x-1) 查询(x,y) 右(y+1,r)
假设现在cnt>=k,我们要递归到左结点,左结点的区间肯定也有3个块
若设为左(l,l1-1) 查询(l1,r1) 右(r1+1,r ) 我们最后肯定查询(l1,r1)
左结点的左区间的数肯定是来自于 父节点的左区间中小于中间值的数
即左结点的左区间的数的个数就是num[dep][x-1]-num[dep][l-1],那么l1就好求了,又知道查询区间的长度,r1也很容易求出来
同理求右结点~并献上一张丑图
图中区间1 2 3 分别是父节点的左 查询 右 区间 ,中间的蓝线分割了左右结点的区间
======
这里简要说明一下为什么int r2=y+num[dep][r]-num[dep][y];
可能不是那么直观(我看了半天)
区间6+区间9=区间3=r-y 而 r2=r-区间9 —> r2=y+区间6 区间6就不用我说了吧
(区间x 代表的是区间长度)
代码:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<stack>
#include<queue>
#include<map>
#include<vector>
#include<set>
#define ls k<<1,l,mid
#define rs k<<1|1,mid+1,r
#define r(x) read(x)
using namespace std;
typedef long long LL;
const int N=1e5+5;
const int M=1e3+5;
const int mod=1e9+7;
const int inf=0x7fffffff;
const double pi=acos(-1);
const double eps=1e-8;
template<class T>
inline void read(T &x)
{
char c;x=1;
while((c=getchar())<'0'||c>'9') if(c=='-') x=-1;
T res=c-'0';
while((c=getchar())>='0'&&c<='9') res=res*10+c-'0';
x*=res;
}
int f[N];
int v[20][N];
int num[20][N];
void build(int l,int r,int dep)
{
if(l==r) return ;
int mid=(l+r)>>1;
int res=mid-l+1;
for(int i=l;i<=r;i++) if(v[dep][i]<f[mid]) res--;
int l1=l,l2=mid+1;
for(int i=l;i<=r;i++){
if(v[dep][i]<f[mid]||(v[dep][i]==f[mid]&&res>0)){
v[dep+1][l1++]=v[dep][i];
if(v[dep][i]==f[mid]) res--;
}
else{
v[dep+1][l2++]=v[dep][i];
}
num[dep][i]=num[dep][l-1]+l1-l;
}
build(l,mid,dep+1);
build(mid+1,r,dep+1);
}
int query(int l,int r,int dep,int x,int y,int k)
{
// cout<<l<<' '<<r<<endl;
if(x==y) return v[dep][x];
int mid=(l+r)>>1;
int cnt=num[dep][y]-num[dep][x-1];
if(cnt>=k){
int l1=l+num[dep][x-1]-num[dep][l-1];
int r1=l1+cnt-1;
return query(l,mid,dep+1,l1,r1,k);
}
else{
int r2=y+num[dep][r]-num[dep][y];
int l2=r2-(y-x-cnt);
// cout<<l2<<' '<<r2<<endl;
return query(mid+1,r,dep+1,l2,r2,k-cnt);
}
}
int main()
{
int n,m;
r(n); r(m);
for(int i=1;i<=n;i++){
r(f[i]);
v[0][i]=f[i];
}
sort(f+1,f+n+1);
build(1,n,0);
// for(int i=0;i<=4;i++){
// for(int j=1;j<=n;j++) cout<<num[i][j]<<' ';
// cout<<endl;
// }
while(m--){
int a,b,c;
r(a); r(b); r(c);
cout<<query(1,n,0,a,b,c)<<endl;
}
return 0;
}