poj 2104 K-th number (模板级划分树)

代码测试通过那一刻,我心都碎了。

昨天晚上开始学习划分树,花了大概一个小时左右弄清楚了划分树的原理(自以为弄清楚了)。然后在网上找了一份代码对照着学习。我喜欢在学习别人代码之前先对那些代码做一些修改,改成我比较习惯的风格。结果不知道是不是我的改动引发了不知名的bug,原代码测试通过之后测试依然能通过,但是我对照着学习却是举步维艰啊!

在上午我自以为已经掌握划分树的情况下,我突然发现我有一个小地方没有弄清楚,一上午的忙活就白瞎了。下午我又重新找了一份代码学习,这次比较顺利,总算是在晚饭之前把自己写了一遍测试通过了。

对照着线段树来描述一下划分树吧:线段树第一层每一层的节点数是不一样的,每一层最大节点的数量是2^(n-1),所有叶节点的数量是n。线段树的这些节点存储了我们需要查询的信息。但是划分树不同,我们看划分树建树实际上是开了几个数组而不是声明一个结构体,存储信息的数组是一个二维数组。可以这样理解,划分树的每一层都存储了数组中所有数据的信息。线段树中的节点存储的信息是我们希望查询的由它的两个子节点的信息加合而来的信息。信息的传递是从下往上,划分树的信息传递则是从上往下。

划分树的原理:划分树用一个数组(在我的代码中是数组a)存储原数组,需要注意的是a数组的每一层都包含了整个原数组的值,只不过是顺序不同。划分树先对数组进行排序,获得数组从小到大的一个比较数组(代码中是b),然后再每一次递归中调换数组中值的位置。以中间值b[mid]为标准,比b[mid]小的数字存入mid左边,比b[mid]大的数值存入右边,在num[i]中记录到a[t][i](t表示当前数组的层数)时,有多少个a[t](这是个数组)中的值存入了mid的左边,有多少个值存入了右边。建树的操作非常简单,麻烦的是查询。

查询的时候需要的是查询区间所在的大区间(这个大区间就是线段树中的子树存的区间,这个区间更新的方式和线段树完全一样),需要查询的区间,查询区间的第k值,以及当前查询操作在划分树的第t层。查询的时候,首先是看查询区间内有多少值在下一层中被分到了左边,如果这个值比需要查询的k值大,说明我们希望得到的查询结果被分配到了左边的区间,如果比k值小,说明结果被分配在了右边。确定了查询结果在哪个区间,然后就是更新在下一层中,在我们查询得到的大区间内,小区间的边界的值。好像说得特别拗口,对照着代码理解吧。同样的操作逐层查询,一直查询到x和y值相等,锁定我们想要的结果然后返回输出。

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define N 100005
int a[31][N],b[N],num[31][N];
int cmp(const void *a,const void *b)
{
	return *(int *)a-*(int *)b;
}
void CreatTree(int x,int y,int t)
{
	if(x==y)
		return ;
	int mid=(x+y)/2;
	int i;
	int lp=x;
	int rp=mid+1;
	for(i=x;i<=y;i++)
	{
		num[t][i]=num[t][i-1];
		if(lp<=mid&&a[t][i]<=b[mid])
		{
			num[t][i]++;
			a[t+1][lp++]=a[t][i];
		}
		else
			a[t+1][rp++]=a[t][i];
	}
	CreatTree(x,mid,t+1);
	CreatTree(mid+1,y,t+1);
	return ;
}
int FindTree(int x,int y,int k,int l,int r,int t)
{
	if(x==y)
		return a[t][x];
	int mid=(l+r)/2;
	int s1,t1;
	s1=num[t][x-1]-num[t][l-1];
	t1=num[t][y]-num[t][x-1];
	if(t1>=k)
		return FindTree(l+s1,l+s1+t1-1,k,l,mid,t+1);
	int s2,t2;
	s2=x-l-s1;
	t2=y-x+1-t1;
	return FindTree(mid+1+s2,mid+s2+t2,k-t1,mid+1,r,t+1);
}
int main()
{
	int n,m;
	while(scanf("%d%d",&n,&m)!=EOF)
	{
		int i;
		for(i=1;i<=n;i++)
		{
			scanf("%d",&a[0][i]);
			b[i]=a[0][i];
		}
		qsort(b+1,n,sizeof(b[0]),cmp);
		CreatTree(1,n,0);
		while(m--)
		{
			int x,y,k;
			scanf("%d%d%d",&x,&y,&k);
			printf("%d\n",FindTree(x,y,k,1,n,0));
		}
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值