我的划分树(详细 注解)

//题目链接:<a href="http://acm.hdu.edu.cn/showproblem.php?pid=4251">http://acm.hdu.edu.cn/showproblem.php?pid=4251</a>

/*
hdu 4251 The Famous ICPC Team Again
别人的代码:(我自己的注解)

注解提供者:GHQ(SpringWater)

自己理解:划分树的思想是将序列按大小,二分位log2(n)层,每一层的信息放在t[d].val[i],(同一层的各部分共享一个val[N])中,用t[d].name[i]记录l到i位置中有多少个
数字划分到左边,算了,还是直接在下面关键部位注释详实一点吧,太难写了!
 */
#include<iostream>   
#include<algorithm>  
using namespace std;  
const int N=100010;
int srted[N];
struct node
{
    int num[N];  // 记录从l到i中有多少个划分到左子树去了
    int val[N];  //记录由上一层划分到下一层中重新排列的数字序列(可能里面有多个[l,r]部分,每个部分按其最初的前后顺序排列,但不一定是升序或降序)
}t[20];//20=log2(N)
int n,m;
void build(int l,int r,int d)//建树  此树并非由节点构成 而是由数组的一段构成  如:d层的l~r是一个节点  
{
    if(l==r) return; //只有一个元素,不能继续划分到两个区间了
    int mid=(l+r)>>1;//取中间那个  
    int midd=srted[mid]; //之前我以为每次 都要从新排序来求这个中位值,后来一想其实最先 排好序的srted【i】,已经得出了每一区间的排序结果,
       //可以直接取[l,r]中位值可获得
    int same=mid-l+1,samed=0,zn=l-1,yn=mid,i;//same初识化为左孩子元素个数    
    //下面减去比midd小的(一定会进入左孩子里边)  剩下的就是==midd并且要进入左孩子的个数  
    //samed是已经插入的数目  
    //zn、yn是左右孩子的开始位置-1,下面会把元素分到两个孩子的区域里边  
    for(i=l;i<=r;++i)      if(t[d].val[i]<midd) --same;
    for(i=l;i<=r;++i)//有点像快排 大的放到后边  小的放前边    相等的看情况  
    {
        if(i==l) t[d].num[i]=0;  //当l为左边边界,左边是其他部分的信息,所以划分到左边的个数为0;
        else t[d].num[i]=t[d].num[i-1];  //l到i划分到左边的个数等于l到i-1划分到左边的个数加上当前是否划到左边的个数;(是++,否不加)
        if(t[d].val[i]<midd)  
        {  
            ++t[d].num[i];//这里是统计从l到i有多少元素进入了左孩子     这是划分树主要用到的数据  
            t[d+1].val[++zn]=t[d].val[i]; // zn的初始值为左边界(l-1),当划分一个进左边,++zn
        }else if(t[d].val[i]>midd)//进入右孩子   
        {  
            t[d+1].val[++yn]=t[d].val[i]; //yn的初始值为右边界(mid),当划分一个进右边,++yn
                }else
        {
            if(samed<same)//名额还没有用完  放左孩子里边  
            {
                ++samed;
                ++t[d].num[i];
                t[d+1].val[++zn]=t[d].val[i];
            }else//方有孩子里边  
                t[d+1].val[++yn]=t[d].val[i];
        }
    }
    build(l,mid,d+1);//建左右子树  
    build(mid+1,r,d+1);
}
int query(int a,int b,int k,int l,int r,int d)//在d层[l,r]的节点里查找[a,b]中的第k大值  
{
    if(a==b) return t[d].val[a];//当已经访问 到只有一个节点的那层了,因为只有一种选择,所以同时可以保证k==1
    int mid=(l+r)>>1;
    int sx=t[d].num[a-1],sy=t[d].num[b]; //sy-sx为[a,b]区间中有多少个划分到左边
    if(a-1<l) sx=0; //当a为左边边界时,左边是别人的部分,所以得sy即为[a,b]区间中有多少个划分到左边
        if(sy-sx>=k)//[a,b]进入左子树的元素>=k  
        return query(l+sx,l+sy-1,k,l,mid,d+1);
    else
    {
        int s1=(a==1?0:a-l-sx); //s1为l到a-1中有多少个划分到右子树,因为a-l为l到a-1的序列个数,减去划分到左边的个数,就为右边的个数了!
        int s2=(b-a+1)-(sy-sx); [a,b]中划分到右边的个数,b-a+1为[a,b]区间的总数,(sy-sx)为[a,b]区间中划分到左边的个数
                                //所以总数减去划分到左边的个数极为划分到右边的个数!
        int nk=k-(sy-sx);//前(sy-sx)大的元素在左子树里  剩下的在右子树里边找  
        return query(mid+1+s1,mid+s1+s2,nk,mid+1,r,d+1); //[mid+1,mid+s1]为[l,a-1]区间划分到右边的数字序列,
        //[mid+1+s1,mid+s1+s2]即为[a,b]区间划分到有子树的数字序列
    }
}
int main()
{
    int cas=1;
    int i,a,b;
    while(cin>>n)
    {
        cout<<"Case "<<cas++<<":"<<endl;
        for(i=1;i<=n;++i)
        {
            cin>>srted[i];
            t[0].val[i]=srted[i];
        }
        sort(srted+1,srted+1+n);
        build(1,n,0);
        cin>>m;
        for(i=1;i<=m;++i)
        {
            cin>>a>>b;
            cout<<query(a,b,(a+b)/2-a+1,1,n,0)<<endl;
        }
    }
    return 0;
} 



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值