划分树

原文

传说中的划分树,只闻其名未见其身。然后搜索了一下划分树的资料,擦擦擦,这不就是同快排的原理+线段树的操作,两者一融合进化成了划分树么。前面两个都会,学习起来倍感轻松。

 

 建树过程: 先对区间[1,n]内所有元素进行排序,未排序之前的数列赋值给线段树的第一层元素(tree[0][i]),然后就是同快排的原理以排序后中间元素为参 照,小于它的放在树下一层的左边,大于它的放在树下一层的右边(划分树建树以中间元素为参照,快排以第一关键元素为参照)。然后再开一个sum数 组,sum[d][i]表示第d层前i个元素有多少个元素小于as[mid](as[mid]为排序后的中间值),这样一层一层建树下去最后建完树后等于 对原数列排好了序。

 

查询过程: 这里最关键了。在查找区间[tl,tr]时,往下查询[tl,tr]左右孩子时,都要对区间[tl,tr]进行更新。

定义两个数s, ss,  d表示第d层, k(k表示要查询的第k元素) :

s 表示区间[l,tl]有多少个元素小于as[mid],  s=sum[d][tl-1];

ss 表示区间[tl,tr]有多少个元素小于as[mid], ss=sum[d][tr]-s;

if(ss>=k)  则下一层查询区间为[l+s,l+s+ss-1];

else  则下一层查询区间为[mid+1+tl-l-s,mid+1+tr-l-s-ss];

自己懒,不愿画图多解释,借用一下小媛姐姐的图。

划分树_第1张图片

分析一下算法复杂度: 建树nlogn+查询mlogn,很强大的说。

/******************************************************************
*  HDU:4251
*  给出一列数,求区间[l,r]内第k小
******************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#define INF 0x3f3f3f3f
#define _Clr(x, y) memset(x, y, sizeof(x))
using namespace std;

typedef long long LL;
const int N = 1e5+10;
int arr[N], as[N]; // 原数组,排序后的数组
int sum[20][N];     // sum[d][i]表示第d层前i个数有多少个小于as[mid]
int tree[20][N];

void build(int d, int l, int r)
{
    int m = (l+r)>>1;
    int lp=l, rp=m+1, lm = m-l+1;
    for(int i=l; i<=m; i++)
        if(as[i]<as[m]) lm--; ///!!! 先假设前mid-l+1个数都等于as[mid],as[i]比它小则减1
    for(int i=l; i<=r; i++)
    {
        if(i==l) sum[d][i] = 0;
        else sum[d][i] = sum[d][i-1];
        if(tree[d][i]==as[m])
        {
            if(lm)
            {
                lm--;
                sum[d][i]++;
                tree[d+1][lp++] = tree[d][i];
            }
            else
                tree[d+1][rp++] = tree[d][i];
        }
        else if(tree[d][i]< as[m])
        {
            sum[d][i]++;
            tree[d+1][lp++] = tree[d][i];
        }
        else tree[d+1][rp++] = tree[d][i];
    }
    if(l==r) return;
    build(d+1, l, m);
    build(d+1, m+1, r);
}

int query_kth(int d, int l, int r, int L, int R, int k)
{
    int s, ss, m=(l+r)>>1;
    if(l==r) return tree[d][l];
    if(l==L) s=0, ss=sum[d][R]; // 特判
    else s=sum[d][L-1], ss=sum[d][R]-s;
    if(ss>=k) return query_kth(d+1, l, m, l+s, l+s+ss-1, k);
    else return query_kth(d+1, m+1, r, m+1+L-l-s, m+1+R-l-s-ss, k-ss);
}

int main()
{
std::ios::sync_with_stdio(false);
#ifndef DEBUG
    freopen("input", "r", stdin);
#endif
    int n, m, a, b, lop=0;
    while(~scanf("%d", &n))
    {
        for(int i=1; i<=n; i++)
        {
            scanf("%d", arr+i);
            tree[1][i] = as[i] = arr[i];
        }

        sort(as+1, as+n+1);
        build(1, 1, n);

        scanf("%d", &m);
        printf("Case %d:\n", ++lop);
        while(m--)
        {
            scanf("%d%d", &a, &b);
            int ans = query_kth(1, 1, n, a, b, (b-a)/2+1);
            printf("%d\n", ans);
        }
    }
    return 0;
}
View Code

 

转载于:https://www.cnblogs.com/khan724/p/4756228.html

你可能感兴趣的:(划分树)