主席树模板poj2104

题意:给一个数组,问区间(L,R)内的第k小个数字,就是把(L,R)内的元素从小到大排序,取第k个数。

主席树的模板题,但是主席树确实很难想,而且也不是很好理解。代码也是抄的大佬的。。。

大概的结构是当n棵权值线段树,每次加入一个数,就要建立一个独立的权值线段树,但是对于这个新建立的权值线段树的左右子树,如果和前一棵树相同就不再建立新的,而是直接沿用前一棵树的。如果没有能用的子树,就只能建立新的结点。

查询第k个数字的时候,如果左区间的数字个数大于等于k就递归左子树,找第k个数,如果小于k就递归右子树,找第(k-区间左右端点的左子树的结点个数之差)个数。

注意:函数参数较多,传参一定要弄对,update函数中的pos和query中的不一样,在update中不管是创建左子树还是右子树,都是第pos个数,而在query中如果是右子树,应该是pos-num个数

#pragma warning(disable:4996)
#include<iostream>
#include<cstring>
#include<cstdio>
#include<climits>
#include<algorithm>
#include<vector>
#include<queue>
#include<set>
using namespace std;
typedef long long ll;
const int MAXN = 100005;
int num[MAXN], sorted[MAXN], cnt, root[MAXN];
//保存排序前的数组,排序后的数组,每个权值线段树根结点
struct  node
{
	int sum, lson, rson;
};
node tree[MAXN<<5];
//tree要开大一点,否则会RE
int create(int sumnum, int Lson, int Rson)
{
	//每一个结点有一个编号
	int id = ++cnt;
	tree[id].lson = Lson;
	tree[id].rson = Rson;
	tree[id].sum = sumnum;
	return id;
}
void update(int& rt, int pre_rt,int pos, int L, int R)
{
	//创建结点
	rt = create(tree[pre_rt].sum + 1, tree[pre_rt].lson, tree[pre_rt].rson);
	if (L == R)return;
	int mid = (L + R) >> 1;
	//创建结点的左儿子或者右儿子
	if (pos <= mid)update(tree[rt].lson, tree[pre_rt].lson, pos, L, mid);
	else update(tree[rt].rson, tree[pre_rt].rson, pos, mid + 1, R);
}
int query(int st,int ed,int L,int R,int k)
{
	//递归到叶子结点,返回排序后数组下标
	if (L == R)return L;
	int mid = (L + R) >> 1,num;
	//区间左右端点的左子树的结点个数之差
	num = tree[tree[ed].lson].sum - tree[tree[st].lson].sum;
	//递归左子树,找第k个数字
	if (k <= num)return query(tree[st].lson, tree[ed].lson, L, mid, k);
	//递归右子树,找第k-num个数字
	else return query(tree[st].rson, tree[ed].rson, mid + 1, R, k - num);
}
int main()
{
	int n, m, i, j, sum, pos;
	while (scanf("%d%d", &n, &m) == 2)
	{
		cnt = 0;
		memset(tree, 0, sizeof(tree));
		memset(root, 0, sizeof(root));
		for (i = 1;i <= n;i++)
		{
			scanf("%d", &num[i]);
			sorted[i] = num[i];
		}
		sort(sorted + 1, sorted + 1 + n);
		sum = unique(sorted + 1, sorted + 1 + n) - sorted-1;
		//排序去重,离散化
		for (i = 1;i <= n;i++)
		{
			//在排序后数组中的位置
			pos = lower_bound(sorted + 1, sorted + 1 + sum, num[i]) - sorted;
			update(root[i], root[i - 1], pos, 1, sum);
		}
		int t1, t2, t3;
		while (m--)
		{
			//查找(L,R)区间,即第R棵权值线段树和第L-1棵权值线段树之差。
			scanf("%d%d%d", &t1, &t2, &t3);
			printf("%d\n", sorted[query(root[t1 - 1], root[t2], 1, sum, t3)]);
		}
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值