POJ 2104 划分树

原数列:8  9  1   4  78  56  4 10  7  5

排序后:1 4 4 5 7 8 9 10 56 78

划分树:(蓝色表示将要进入左子树)

[8 9 1 4 78 56 4 10 7 5]

[1 4 4 7 5 ] [8 9 78 56  10]

[1 44] [7 5] [89 10 ] [78  56]

[1 4][4][5][7] [8 9 ] [10] [56][78]

记录最左边到当前结点进入左子树的个数

[0 0 1 2 2 2 3 3 4 5]

[1 2 3 3 3] [4 5 5 5 6]

[1 2 2][2 3] [4 5 5][ 5 6]

对应的线段树区间:

[1,10]

[1,5][6,10]

[1,3][4,5][6,8][9,10]


划分树是类似快排的思想,建立在线段树(区间记为[LR])的基础上,先将原数列排序(记为SortNum),每次将当前数的跟SortNum[mid]mid=( L + R)>>1)比较,小的进入左子树,大的进入右子树,并记录最左边到当前结点进入左子树的个数。

查找:

设当前要查找的区间为[st],当前所在线段树区间为[LR]

进入左子树:

则记 x= toleft[row][s - 1] - toleft[row][L – 1],即为[Ls-1]区间里进入左子树的数的个数,记y= toleft[row][t] - toleft[row][L – 1],即为区间[Lt]区间进入右子树的数的个数,假如k<=(y -x)即小于[st]区间进入左子树的个数,则查找左子树的区间[L+ xL+ y -1];

进入右子树:

rx[Ls-1]区间里进入右子树的数的个数,ry为区间[Lt]区间进入右子树的数的个数

rx= s – L -xry= t – L -y,假如k> (y – x)即大于[st]区间进入左子树的个数,则查找右子树

的区间[mid+ 1 + rx, mid + 1 + ry],并更新k= k – (y - x)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
#define MAXN 100000
int num[MAXN + 10];
int SortNum[MAXN + 10];
int value[20][MAXN + 10];
int toleft[20][MAXN  + 10];
void  build_tree(int s,int t,int row)
{
  if(s == t)
    {
      return;
    }
  int mid = (s + t)>>1;
  int same;
  int lchild = s;
  int rchild = mid + 1;
  int i;
  same = mid - s + 1;
  for(i = s;i <= t;i++)
    {
      if(value[row][i] < SortNum[mid])
	{
	  same--;
	}
    }//记录跟SortNum[mid]一样的数将进入左子树的个数
  for(i = s;i <= t;i++)
    {
      toleft[row][i] = toleft[row][i - 1];
      if(value[row][i] < SortNum[mid])//进入左子树
	{
	  value[row + 1][lchild++]= value[row][i];
	  toleft[row][i]++;
	 
	}
      else if(value[row][i] > SortNum[mid])//进入左子树
	{
	  value[row + 1][rchild++] = value[row][i];
	}
      else if(same > 0)//跟SortNum[mid]一样的数进入左子树
	{
	  value[row + 1][lchild++] = value[row][i];
	  toleft[row][i]++;
	  same--;
	}
      else//跟SortNum[mid]一样的数进入右子树
	{
	  value[row + 1][rchild++] = value[row][i];
	}
    }
  build_tree(s,mid,row + 1);
  build_tree(mid + 1,t,row + 1);
}
int query(int L,int R,int s,int t,int k,int row)
{
  //  cout<<s<<"  "<<t<<endl;
  if(s == t)
    {
      return value[row][s];
    }
  int x,y;
  int rx,ry;
  int mid = (L + R) >> 1;
  x = toleft[row][s - 1] - toleft[row][L - 1];//[L,s-1]区间里进入左子树的数的个数
  y = toleft[row][t] - toleft[row][L - 1];//区间[L,t]区间进入右子树的数的个数
  if(y - x >= k)//y-x为[s,t]区间进入左子数的个数
    {
      return query(L,mid,L + x,L + y -1,k,row + 1);
    }
  else
    {
      rx = s - L -x;//[L,s-1]区间里进入右子树的数的个数
      ry = t - L  - y;//区间[L,t]区间进入右子树的数的个数
      return  query(mid + 1,R,mid + 1 +  rx,mid + 1 + ry,k - (y -x),row + 1); 
    }
  
}
int main()
{
  int n,m;
  int i;
  int s,t,k;
  int ans;
  while(scanf("%d %d",&n,&m) !=EOF)
    {
      memset(toleft,0,sizeof(toleft));
      for(i = 1;i <= n;i++)
	{
	  scanf("%d",&value[0][i]);
	}
      memcpy(SortNum,value,sizeof(value[0]));
      sort(&SortNum[1],&SortNum[n + 1]);
      build_tree(1,n,0);
      for(i = 0;i < m;i++)
	{
	  scanf("%d %d %d",&s,&t,&k);
	  ans = query(1,n,s,t,k,0);
	  printf("%d\n",ans);
	  
	}    
    }
  return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值