【原】 POJ 2104 K-th Number 线段树 划分树 合并树 解题报告
2010-11-05 16:55
615 查看
方法:
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]
相关文章推荐
- [POJ]2104 K-th Number 主席树&线段树合并&整体二分
- Poj 2104 K-th Number 主席树 解题报告
- 区间第K大数——划分树(POJ2104解题报告)
- 线段树 划分树 合并树 解题报告
- POJ 2104 K-th Number(可持久化线段树)
- POJ 2104 K-th Number 线段树
- POJ_2104_K-th Number_线段树(归并树)
- 线段树的典型应用之POJ 2823 Sliding Window解题报告
- POJ 2104 K-th Number 初涉划分树
- POJ2104 K-th number 函数式线段树
- POJ 2104 解题报告
- [poj]-2104-K-th Number-可持久化线段树
- poj 2104 K-th Number(划分树)
- POJ 2104 K-th number 主席树 函数式线段树
- poj2104 K-th Number(划分树)
- poj 2104 K-th Number (划分树)
- POJ 3468 线段树 解题报告
- POJ 2104 K-th Number(可持久化线段树-求第K大)
- POJ 3468 A Simple Problem with Integers (线段树成段更新) 解题报告
- POJ 2104 K-th Number(划分树)