您的位置:首页 > 其它

【原】 POJ 2104 K-th Number 线段树 划分树 合并树 解题报告

2010-11-05 16:55 615 查看
 

http://poj.org/problem?id=2104

方法:
1、划分树,是平衡树:数组排序nlgn,建树nlgn,m次查询mlgn,总复杂度为O(nlgn+mlgn)
    划分树就是利用类似线段树的树型结构记录划分元素(最终排序)的过程。
    划分树是一种树形结构的二维数组,由四个数组构成,具体见程序
    建树:
    从树的root(第一层)开始对原始数组进行划分,小于中位数的依原相对顺序放入左孩子节点,大于的放入右孩子节点,
    并同时记录该节点(root)中截止到每个元素的分到左孩子节点的元素个数。
    注意要小心处理等于中位数的元素,不能一味的将其放入左孩子或是右孩子,这样会破坏树的平衡。
    再递归的对左右孩子(下一层)做同样的工作。
    复杂度:T(n)=2T(n/2)+O(n)=O(n*lgn)

    查询:
    查询数组中某个截断a[i...j]中的第k个元素。
    从root开始,计算a[i...j]中分到左孩子的元素数s。
    若s<=k,则表明k-th元素在该节点的左孩子中,于是计算新的数组区间,递归的去左孩子中找新区间的第k-th元素。
    若s>k,则表明k-th元素在该节点的右孩子中,于是计算新的数组区间,递归的去右孩子中找新区间的第(k-s)-th元素。
    复杂度:由于每次递归都下降一层,而元素数为n的数组形成的树的高度为lgn,所以复杂度为lgn

2、归并树+2次二分+1次查询(lgn次枚举):
    归并树就是利用类似线段树的树型结构记录合并排序的过程。把归并排序过程中的各区间排序后的结果,用线段树储存起来
    归并树可以说是线段树+归并排序,线段树是每个区间递归下去的,而归并排序正好是拥有相似的性质,树生成即对每个
    区间排好序,用一个二维数组来记录。

    建树:
    利用MergeSort的递归特性进行建树,从底下往上建,类似于合并的过程。复杂度nlgn

    查询:
    建立归并树后我们得到了序列a[1...n]的非降序排列,由于此时a[1...n]内元素对于任何区间的rank是非递减的,因此a[1...n]
    中属于指定区间[b,e]内的元素的rank也是非递减的,所以我们可以用二分法枚举a[1...n]中的元素并求得它在[b,e]中的rank值,
    直到该rank值和询问中的rank值相等(复杂度lgn);
    那对于a[1...n]中的某个元素val,如何求得它在指定区间[s,t]中的rank?这就要利用到刚建好的归并树:我们可以利用类似线
    段树的query[s,t]操作找到所有属于[s,t]的子区间(复杂度lgn),然后累加val分别在这些子区间内的rank,得到的就是val在
    区间[s,t]中的rank,由于属于子区间的元素的排序结果已经记录下来,所以val在子区间内的rank可以通过二分法得到(复杂度lgn)。
    上面三步经过了三次二分操作(query也是种二分),于是每次询问的复杂度是O(logn*logn*logn)

3、Partition找k-th元素。m次查询复杂度为m*n,而且为了不改变原始数组而影响下一次查询,还需要复制a[i...j]到临时数组。
    复杂度太高,会导致TLE

Description

YouareworkingforMacrohardcompanyindatastructuresdepartment.Afterfailingyourprevioustaskaboutkeyinsertionyouwereaskedtowriteanewdatastructurethatwouldbeabletoreturnquicklyk-thorderstatisticsinthearraysegment. 
Thatis,givenanarraya[1...n]ofdifferentintegernumbers,yourprogrammustansweraseriesofquestionsQ(i,j,k)intheform:"Whatwouldbethek-thnumberina[i...j]segment,ifthissegmentwassorted?" 
Forexample,considerthearraya=(1,5,2,6,3,7,4).LetthequestionbeQ(2,5,3).Thesegmenta[2...5]is(5,2,6,3).Ifwesortthissegment,weget(2,3,5,6),thethirdnumberis5,andthereforetheanswertothequestionis5.

Input

Thefirstlineoftheinputfilecontainsn---thesizeofthearray,andm---thenumberofquestionstoanswer(1<=n<=100000,1<=m<=5000). 
Thesecondlinecontainsndifferentintegernumbersnotexceeding109bytheirabsolutevalues---thearrayforwhichtheanswersshouldbegiven. 
Thefollowingmlinescontainquestiondescriptions,eachdescriptionconsistsofthreenumbers:i,j,andk(1<=i<=j<=n,1<=k<=j-i+1)andrepresentsthequestionQ(i,j,k).

Output

Foreachquestionoutputtheanswertoit---thek-thnumberinsorteda[i...j]segment.

SampleInput

73

1526374

253

441

173

SampleOutput

5

6

3

Hint

Thisproblemhashugeinput,sopleaseusec-styleinput(scanf,printf),oryoumaygottimelimitexceed.

 

[code][code]constintN=100001;


 


//划分树:left,right,mid是其对应的数组的index


structSegTree


{


intleft;


intright;


intmid(){returnleft+((right-left)>>1);}


};


 


//tree[1...N*4-1]可看做线段树,其中的下标对应关系类似于heap,1为root,2*i为左孩子,2*i+1为右孩子


SegTreetree[1<<19];


//val[0]
...val[19]
,其中0~19对应着树的每一层,0为root层


intval[20]
;


//某节点tree[i]中的某个元素val[j][k],toleft[j][k]表示区间[tree[i].left,val[j][k]]中有多少个元素被分到左边


inttoLeft[20]
;


//对输入数组已排序的结果,用来找中位数


intsortArr
;


 


//建立划分树,复杂度n*lgn


void__BuildSegTree(intl,intr,intd,intidx)


{


tree[idx].left=l;


tree[idx].right=r;


 


if(l==r)


return;


 


inti;


intlpos,rpos;


intmidIdx,midVal;


intleftSame;//分到左边的和midVal相同的数的个数。这样的数不能都分到左边或右边,不然会破坏树的平衡


 


midIdx=tree[idx].mid();


midVal=sortArr[midIdx];


leftSame=midIdx-l+1;//先假设分到左边的数都==midVal


for(i=l;i<=r;++i)


{


if(val[d][i]<midVal)


--leftSame;


}


 


//由d层形成d+1层


lpos=l;


rpos=midIdx+1;


for(i=l;i<=r;++i)


{


if(i==l)


toLeft[d][i]=0;


else


toLeft[d][i]=toLeft[d][i-1];


 


if(val[d][i]<midVal)


{


++toLeft[d][i];


val[d+1][lpos++]=val[d][i];


}


elseif(val[d][i]>midVal)


val[d+1][rpos++]=val[d][i];


else


{


if(leftSame>=0)


{


val[d+1][lpos++]=val[d][i];


++toLeft[d][i];


--leftSame;


}


else


val[d+1][rpos++]=val[d][i];


}


}


 


//递归地对d+1层的左右子节点做构造


__BuildSegTree(l,midIdx,d+1,idx*2);


__BuildSegTree(midIdx+1,r,d+1,idx*2+1);


}


 


voidBuildSegTree(intl,intr)


{


__BuildSegTree(l,r,0,1);


}


 


//查询划分树中某个区间[l,r]内第k个元素


//复杂度lgn


int__Query(intl,intr,intk,intd,intidx)


{


if(l==r)


returnval[d][l];


 


inti;


ints,ss;


intb,bb;


intnewl,newr;//递归的新区间


 


if(l==tree[idx].left)


{


s=toLeft[d][r];//s为[l,r]中被分到左孩子的个数


ss=0;//ss为[tree[idx].left,l-1]中被分到左孩子的个数


}


else//l>tree[idx].left


{


s=toLeft[d][r]-toLeft[d][l-1];


ss=toLeft[d][l-1];


}


 


if(s>=k)//[l,r]中的k-th元素一定在左孩子中


{


newl=tree[idx].left+ss;


newr=newl+s-1;


return__Query(newl,newr,k,d+1,idx*2);


}


else


{


b=(r-l+1)-s;//b为[l,r]中被分到右孩子的个数


bb=((l-1)-tree[idx].left+1)-ss;//bb为[tree[idx].left,l-1]中被分到右孩子的个数


newl=tree[idx].mid()+1+bb;


newr=newl+b-1;


return__Query(newl,newr,k-s,d+1,idx*2+1);


}


}


 


intQuery(intl,intr,intk)


{


return__Query(l,r,k,0,1);


}


 


//由于题目中所有数字的绝对值都不相同,所以不用花费O(N)去计算分到左边的和midVal相同的个数以保持树的平衡


/*


void__BuildSegTree(intl,intr,intd,intidx)


{


tree[idx].left=l;


tree[idx].right=r;




if(l==r)


return;




inti;


intlpos,rpos;


intmidIdx,midVal;




midIdx=tree[idx].mid();


midVal=sortArr[midIdx];




//由d层形成d+1层


lpos=l;


rpos=midIdx+1;


for(i=l;i<=r;++i)


{


if(i==l)


toLeft[d][i]=0;


else


toLeft[d][i]=toLeft[d][i-1];




if(val[d][i]<=midVal)


{


++toLeft[d][i];


val[d+1][lpos++]=val[d][i];


}


else


val[d+1][rpos++]=val[d][i];


}




//递归地对d+1层的左右子节点做构造


__BuildSegTree(l,midIdx,d+1,idx*2);


__BuildSegTree(midIdx+1,r,d+1,idx*2+1);


}


*/


 


voidrun2104()


{


//ifstreamin("in.txt");


 


inti,j;


intn,m;


intl,r,k;


scanf("%d%d",&n,&m);


//in>>n>>m;


 


for(i=1;i<=n;++i)


{


scanf("%d",&(val[0][i]));


//in>>val[0][i];


sortArr[i]=val[0][i];


}


 


QuickSort(sortArr+1,n);


BuildSegTree(1,n);


 


/*


for(i=0;i<=4;++i)


{


for(j=1;j<=n;++j)


cout<<val[i][j]<<"";


cout<<endl;


}


cout<<endl;




for(i=0;i<=4;++i)


{


for(j=1;j<=n;++j)


cout<<toLeft[i][j]<<"";


cout<<endl;


}


*/




 


while(m--&&scanf("%d%d%d",&l,&r,&k))


//while(m--&&in>>l>>r>>k)


printf("%d\n",Query(l,r,k));


}

[/code]
[/code]

 

 


[code]
[code]constintN=100001;


 


//归并树,可以看做是归并排序的产物


//left,right,mid是其对应的数组的index


structMergeTree


{


intleft;


intright;


intdepth;


intmid(){returnleft+((right-left)>>1);}


};


 


inta
;


 


//tree[1...N*4-1]可看做线段树,其中的下标对应关系类似于heap,1为root,2*i为左孩子,2*i+1为右孩子


MergeTreetree[1<<19];


//val[0]
...val[19]
,其中0~19对应着树的每一层,0为root层


intval[20]
;


 


//由depth为d+1的两个节点Merge成depth为d的节点


voidMerge(intd,intidx)


{


intlb,le,rb,re;


inti,j;


intbIdx;


 


lb=tree[idx].left;


le=tree[idx].mid();


rb=le+1;


re=tree[idx].right;


bIdx=lb;


 


while(lb<=le&&rb<=re)


{


if(val[d+1][lb]<val[d+1][rb])


val[d][bIdx++]=val[d+1][lb++];


else


val[d][bIdx++]=val[d+1][rb++];


}


 


while(lb<=le)


val[d][bIdx++]=val[d+1][lb++];


while(rb<=re)


val[d][bIdx++]=val[d+1][rb++];


}


 


void__BuildMergeTree(intd,intidx,intl,intr)


{


tree[idx].depth=d;


tree[idx].left=l;


tree[idx].right=r;


 


if(l==r)


{


val[d][l]=a[l];


return;


}


 


intmidIdx=tree[idx].mid();


__BuildMergeTree(d+1,idx*2,l,midIdx);


__BuildMergeTree(d+1,idx*2+1,midIdx+1,r);


Merge(d,idx);


}


 


voidBuildMergeTree(intl,intr)


{


__BuildMergeTree(0,1,l,r);


}


 


int__Query(intb,inte,intelement,intidx)


{


intl,r,d;


intmid;


intcnt;


 


l=tree[idx].left;


r=tree[idx].right;


d=tree[idx].depth;




//找到[b...e]包含的所有子区间,停止往下递归


if(b<=l&&e>=r)


{


//找到val[d][l...r]中<=element的最大元素的位置


//invairant:val[d][b]<=element<val[d][e]


b=l-1;


e=r+1;


while(b+1!=e)


{


mid=b+((e-b)>>1);


if(val[d][mid]<=element)


b=mid;


else


e=mid;


}


if(b==l-1)


return0;


else


returnb-l+1;


}


else


{


cnt=0;


mid=l+((r-l)>>1);


if(b<=mid)


cnt+=__Query(b,e,element,idx*2);


if(e>=mid+1)


cnt+=__Query(b,e,element,idx*2+1);


returncnt;


}


}


 


//查找在a[b...e]中有多少个元素<=element


//必须是计算<=的元素数,这样才能保证所以第一个==k的元素是答案


intQuery(intb,inte,intelement)


{


return__Query(b,e,element,1);


}


 


voidrun2104_MergeTree()


{


//ifstreamin("in.txt");


 


inti,j;


intn,m;


intb,e,k;


intmid;


intcnt;


intl,r;


scanf("%d%d",&n,&m);


//in>>n>>m;


 


for(i=1;i<=n;++i)


{


scanf("%d",&a[i]);


//in>>a[i];


//sortArr[i]=a[i];


}


 


BuildMergeTree(1,n);




//已排好序的a[1...n]中对于某区间[b,e]可能有多个数得到的<=其本身的元素数与指定的k相同,


//这是由于其中有些数不在原始a[b,e]中,但是他们大于k-thnumber,小于k-thnumber的元素的<=其本身的元素数是不会等于k的


//例如某区间中[6,2,3],k-thnumber为6,小于6的5返回的<=其本身的元素数为2


//所以第一个==k的元素才是答案


//所以二分枚举时的invariant:Query(val[0][l])<k<=Quert(val[0][r])


while(m--&&scanf("%d%d%d",&b,&e,&k))


//while(m--&&in>>b>>e>>k)


{


l=1-1;


r=n+1;


//invariant:Query(val[0][l])<k<=Quert(val[0][r])


while(l+1!=r)


{


mid=l+((r-l)>>1);


cnt=Query(b,e,val[0][mid]);


if(cnt<k)


l=mid;


else


r=mid;


}


printf("%d\n",val[0][r]);


}


}

[/code]
[/code]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: