<img src="https://img-my.csdn.net/uploads/201108/17/0_1313604667udps.gif" alt="" />
图片来自:http://www.cnblogs.com/crazyapple/p/3223954.html
http://acm.hdu.edu.cn/showproblem.php?pid=2665
poj的改变一下输入就可以过
http://poj.org/problem?id=2104
http://poj.org/problem?id=2761
下面那个版本的只能过poj2104 --建树的时候有问题,分析详见下面解释
#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<string.h>
using namespace std;
#define N 100010
int sorted[N]; //排序完的数组
int toleft[30][N]; //toleft[i][j]表示第i层从1到j分入左边的个数
int tree[30][N]; //表示每层每个位置的值
int flag;
void buildingTree(int le,int r,int dep)
{
if(le==r) return;
int mid = (le+r)>>1;
int i,sum = mid-le+1; //表示等于中间值而且被分入左边的个数
for(i=le;i<=r;i++)
{
if(tree[dep][i]<sorted[mid]) sum--;
}
int lpos=le;
int rpos=mid+1;
for(i=le;i<=r;i++)
{
if(tree[dep][i]<sorted[mid]) tree[dep+1][lpos++]=tree[dep][i];//比中间的数小,分入左边
else if(tree[dep][i]==sorted[mid]&&sum>0)
{//等于中间的数值,分入左边,直到sum==0后分到右边
tree[dep+1][lpos++]=tree[dep][i];
sum--;
}
else tree[dep+1][rpos++]=tree[dep][i];//右边
toleft[dep][i] = toleft[dep][le-1] + lpos - le;
//从1到i放左边的个数=1至le-1去左的个数+这层区间的le至i又去左的个数(lpos-le)
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>buildingTree(le,mid,dep+1);
buildingTree(mid+1,r,dep+1);
}
//查询区间第k大的数,[L,R]是大区间,[le,r]是要查询的小区间
int queryTree(int L,int R,int le,int r,int dep,int k)
{
if(le==r) return tree[dep][le];
int mid = (L+R)>>1;
int cnt = toleft[dep][r] - toleft[dep][le-1]; //[le,r]中位于左边的个数
<span style="white-space:pre"> </span>
if(cnt>=k)
{
int newl = L + toleft[dep][le-1] - toleft[dep][L-1];
//L+(L-1至le-1)区间被放在左边的个数(l-1和L-1的-1原因:toleft[L]表示包括1至L区间被放入左个数)\
而现在要知道L至le的区间有几个被放入<=mid内(左区间)\
(L+:因为左区间起点是L。+:★相对位置不变而任性--即(L-1至le-1)被放入左的位置一定是要查的前面)\
ex:2 3 1 4 我查区间[3 4]第一个(被放左),(L-1至le-1)区间2也被放左,那么成2,1相对位置不变(下层查区间[2,2])
int newr = newl + cnt - 1; //左端点加上查询区间会被放在左边的个数
return queryTree(L,mid,newl,newr,dep+1,k);
}
else
{
int newr = r + toleft[dep][R] - toleft[dep][r]; //\
其实就是这个= R-((R-r+1)-( toleft[dep][R] - toleft[dep][r]))+1;--误我好久.\
即新右边界(★相对位置不变而任性)=R-去右边的个数(区间长度-去左个数)\
//计算[r+1至R]去左区间的个数,\
ex:4 3 1 2.我查区间[2 3]第二个,此时区间[4,4]被放左个数为1,即下层成1 2 4 3
int newl = newr - (r-le+1 - cnt)+1;
//r-le+1:区间长度.(r-le+1 - cnt):去了右区间的个数.\
newr-去了右区间的个数+1(ex:4-1+1=4即只有1个去了右边(即要查的),即区间[4,4])\
---当然也可以1.先求newl=mid+le-L+1-(toleft[dep][le-1] - toleft[dep][L-1])\
起点=mid+[L,le)去了右区间的个数(★相对位置不变而任性)\
2.后求newr=newl+r-le-cnt([le,r]去了右区间的个数,即左起点+区间长-去左个数)
<span style="white-space:pre"> </span>//int newl=mid+le-L+1-(toleft[dep][le-1] - toleft[dep][L-1]);
<span style="white-space:pre"> </span>//int newr=newl+r-le-cnt;
//可以写成newl+(r-le+1)-cnt-1;r-le+1:区间长度.(r-le+1 - cnt):去了右区间的个数.\
-1:因为这个newl是闭区间(包含newl.ex:长度为1的[newl,newr]\
即算newr时,(r-le+1 - cnt):去了右区间的个数=1,若不减1则newr>newl成长度为2了)
return queryTree(mid+1,R,newl,newr,dep+1,k-cnt);
}
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int n,m,i,s,t,k;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++)
{ scanf("%d",&tree[0][i]); sorted[i] = tree[0][i]; }
sort(sorted+1,sorted+1+n);
buildingTree(1,n,0);
while(m--)
{
scanf("%d%d%d",&s,&t,&k);
printf("%d\n",queryTree(1,n,s,t,0,k));
}
}
return 0;
}
/*
输出这组数据的建树时候tree[dep+1][i]对比就可以发现
10 10
7 6 2 4 0 8 3 6 5 3
3 4 1
1 6 2
4 9 5
4 9 4
9 10 1
3 6 3
3 3 1
8 9 1
5 8 3
5 7 2
原因:建(6 6 5 7 8)时候的左半区间(应该是6 5 6;而下面那种建树6 6 5)
的左半(应该5 6;而下面那种建树0 6,因为此时sorted[mid]=5,所以这层6 6都
被放到下层的右边,故下层左边就是0了)
*/
</pre><pre name="code" class="plain">也不知道别人会怎么看这代码里任性的注释
划分树定义为,:求区间第K大数
查找整序列的第k大值往往采用。然而此方法会破坏原序列,并且需要O(n)的时间复杂度。抑或使用二叉平衡树进行维护,此方法每次查找时间复杂度仅为O(logn)。然而此方法丢失了原序列的顺序信息,无法查找出某区间内的第k大值。划分树的基本思想就是对于某个区间,把它划分成两个子区间,左边区间的数小于右边区间的数。查找的时候通过记录进入左子树的数的个数,确定下一个查找区间,最后范围缩小到1,就找到了
她的每一个节点保存区间 [lft,rht ]所有元素,
元素排列顺序与原数组(输入)相同,但是,
两个子树的元素为该节点所有元素排序后(mid - lft + 1)个进入左子树,其余的到右子树,
同时维护一个num域,num[i] 表示[lft,i]这些点有多少进入了左子树。
真实的树由深度和区间存储
#include <stdio.h>
#include <algorithm>
#define N 100001
int sum[20][N],tr[20][N],sor[N];
void build(int l,int r,int d)
{
if(l == r)
return ;
int mid = (l + r) >> 1,i,lp = l,rp = mid + 1;
for(i = l; i <= r; ++i)
{
sum[d][i] = sum[d][i-1]; //★看下两行sum[d][i]表:d层放在i以左的个数
if(tr[d][i] <= sor[mid] && lp <= mid) //放到左子树
tr[d+1][lp++] = tr[d][i],++sum[d][i];
else //放到右子树
tr[d+1][rp++] = tr[d][i];
}
build(l, mid, d + 1);
build(mid + 1, r, d + 1);
}
int query(int s,int t,int k,int l,int r,int d)
{//询问区间[s,t]
if(s == t)
return tr[d][s];
int sl,tl;
sl = sum[d][s-1] - sum[d][l-1]; //sl 表示[l,s-1] 分到左边个数
tl = sum[d][t] - sum[d][s-1]; //tl 表示[s,t] 分到左边个数
int mid = (l + r) >> 1;
if(tl >= k)
return query(l + sl,l + sl + tl - 1, k, l, mid, d + 1);
//因为划分树特点:换到同一边的数相对于原数组中元素★相对位置不变,
//故可以这么任性的变成下一深度的(l + sl,l + sl + tl - 1)区间找k大
//下一查起点l + sl:左区间起点l + s左边去了左边的个数 (因★相对位置不变而任性地肯定是[s,t]去左边的起点)
//下一查终点l + sl + tl - 1:即长度为tl的[l+s1,l+s1+t1)因★相对位置不变而任性
int b = s - l - sl, //b 表示[l,s-1] 分到右边个数
bb = t - s + 1 - tl; //bb 表示[s,t] 分到右边个数
return query(mid + 1+b,mid + b + bb,k - tl,mid + 1, r, d + 1);
//右区间起点必mid + 1:再+b即是下一查起点,同上的任性
//下一查终点:起点+bb-1(闭区间), k - tl:去左区间t1个,故..
//即下次区间[mid + 1+b,mid + 1+b+bb)
}
int main()
{
int n,m,l,r,k,i;
scanf("%d%d",&n,&m);
for(i = 1; i <= n; ++i)
{
scanf("%d",sor + i);
tr[0][i] = sor[i];
}
std::sort(sor + 1,sor + 1 + n);
build(1,n,0);
while(m--)
{
scanf("%d%d%d",&l,&r,&k);
printf("%d\n",query(l,r,k,1,n,0));
}
return 0;
}