线段树一维的刷差不多了,求区间第K数一直卡着。
划分树和归并树都可以求,比较了一下时间效率,划分树比归并树快了很多,而且POJ有个求区间第K数的题用归并树居然过不去。
鉴于时间短,我决定把划分树给弄明白= =。。借用下小HH的图。
划分树和归并树都是用线段树作为辅助的,原理是基于 快排 和 归并排序 的。
划分树的建树过程基本就是模拟快排过程,取一个已经排过序的区间中值,然后把小于中值的点放左边,大于的放右边。并且记录d层第i个数之前(包括i)小于中值的放在左边的数。具体看下面代码注释。
关键是询问过程,具体见图。CSDN现在上传图片质量好差啊啊啊啊啊啊 。
hdu3473是求中位数的(可以证得,差值和最小的一定是中位数(如果是偶数个的话,中间两个任意以一个均可)),但是涉及求和,涉及求得在区间[l,r]中,中位数之前元素的和(排好序后)。这样的话,在建树过程增加一个二维数组,第d层下标<=i之前元素的和。然后找的时候,求区间差值和即可。
附,poj2104代码,买一赠一,poj2761和2104基本一样
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MID(x,y) ( ( x + y ) >> 1 )
#define L(x) ( x << 1 )
#define R(x) ( x << 1 | 1 )
#define BUG puts("here!!!")
#define LL long long
using namespace std;
const int MAX = 100010;
LL s[MAX];
class Parti_tree{
public :
class Tnode{ // 我的一维线段树定义
public :
int l,r;
int len() { return r - l;}
int mid() { return MID(l,r);}
bool in(int ll,int rr) { return l >= ll && r <= rr; }
void lr(int ll,int rr){ l = ll; r = rr;}
};
Tnode node[MAX<<2];
int num_left[20][MAX], seg[20][MAX],sa[MAX];//sa数组存sort后的结果
//seg数组存的是d层划分后的数字 (类似快排Partation (d-1)次后的结果)
//num_left存的是d层在i之前(包括i)小于 sa[mid] 的数的数目
void init()
{
memset(seg,0,sizeof(seg));
memset(num_left,0,sizeof(num_left));
memset(node,0,sizeof(node));
}
void build(int s,int t)
{
sort(sa+s,sa+t+s);
Parti_build(1,s,t,1);
}
int query(int s,int t,int k)
{
return find_rank(1,s,t,1,k);
}
void Parti_build(int t,int l,int r,int d)
{
node[t].lr(l, r);
if( node[t].len() == 0 ) return ;
int mid = MID(l, r), lsame = mid - l + 1;
for(int i=l; i<=r; i++)//首先确定分到每一侧的数的数目
if( seg[d][i] < sa[mid] )//因为相同的元素可能被分到两侧
lsame--;
int lpos = l,rpos = mid+1;
for(int i=l; i<=r; i++)
{
if( i == l )
num_left[d][i] = 0;
else
num_left[d][i] = num_left[d][i-1];
if( seg[d][i] < sa[mid] )
{
num_left[d][i]++;
seg[d+1][lpos++] = seg[d][i];
}
if( seg[d][i] > sa[mid] )
seg[d+1][rpos++] = seg[d][i];
if( seg[d][i] == sa[mid] )
if( lsame > 0 ) // 如果大于0,说明左侧可以分和sa[mid]相同的数字
{
lsame--;
num_left[d][i]++;
seg[d+1][lpos++] = seg[d][i];
}
else //反之,说明左侧数字已经分满了,就分到右边去
seg[d+1][rpos++] = seg[d][i];
}
Parti_build(L(t), l, mid, d+1);
Parti_build(R(t), mid+1, r, d+1);
}
int find_rank(int t,int l,int r,int d,int val)
{
if( node[t].len() == 0 ) return seg[d][l];
int s,ss; //s表示区间[l,r]有多少个小于sa[mid]的数被分到左边
if( l == node[t].l )
ss = 0;
else
ss = num_left[d][l-1];
s = num_left[d][r] - ss;//ss表示从当前区间的L到l-1有多少个小于sa[mid]的数被分到左边
if( s >= val )
return find_rank(L(t), node[t].l+ss, node[t].l+ss+s-1, d+1, val);
else
{
int mid = node[t].mid();
int bb = l - node[t].l - ss; //表示从当前区间L到l-1有多少个分到右边
int b = r - l + 1 - s; //表示[l,r]有多少个分到右边
return find_rank(R(t), mid+bb+1, mid+bb+b,d+1,val-s);
}
}
};
Parti_tree t;
int main()
{
int n,m,x,y,k;
while( ~scanf("%d%d",&n,&m) )
{
t.init();
for(int i=1; i<=n; i++)
{
scanf("%d",&t.sa[i]);
t.seg[1][i] = t.sa[i];
}
t.build(1,n);
while( m-- )
{
scanf("%d%d%d",&x,&y,&k);
int ans = t.query(x, y, k);
printf("%d\n",ans);
}
}
return 0;
}