poj 2104 or poj2761 or hdu2665 划分树

来源:互联网 发布:怎样开好淘宝店铺 编辑:程序博客网 时间:2024/06/05 05:12
<img src="http://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=2665poj的改变一下输入就可以过http://poj.org/problem?id=2104http://poj.org/problem?id=2761下面那个版本的只能过poj2104 --建树的时候有问题,分析详见下面解释#include<iostream>#include<stdio.h>#include<algorithm>#include<string.h>using namespace std;#define N 100010int 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 107 6 2 4 0 8 3 6 5 3 3 4 11 6 24 9 54 9 49 10 13 6 33 3 18 9 15 8 35 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 100001int 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
原创粉丝点击