poj 2104 or poj2761 or hdu2665 划分树

11 篇文章 0 订阅
7 篇文章 0 订阅
<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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值