主席树讲解

1.定义

主席树又称可持久化线段树,能够保存线段树的历史版本。之所以命名为主席树是因为其发明人黄嘉泰。据说是他在考场上写出来的,,tql。。
学习主席树,需要对线段树有一定了解,线段树请看线段树算法讲解

2.具体实现

主席树的节点的子节点不再是类似于线段树,不再是2*x和2 * x +1.
需要在结构体里面存其子节点,并且,每更新一次都需要新建一个根,所以还需要一个roots数组来记录根。

const int maxn = 1e5 + 10;
struct node{
	int l, r;//左子树,右子树 
	int data;
	node()
	{
		data = 0;
	}
}tree[maxn * 20];

int roots[maxn];
int tot = 1;

首先是建树。
以求给定区间内第k小的数为例。
建树类似于线段树的单点修改。

void update(int num, int &root, int l, int r)//对root进行引用&是其精髓所在 
{
	tree[tot++] = tree[root];//新建一个节点,将其赋值为上一个根的内容 
	root = tot - 1;//将当前根赋值为新建节点 
	tree[root].data++;//更新新建的根 
	if(l == r)//找到叶子节点 
		return;
	int mid = (l + r) >> 1;
	if(mid >= num)//新插入的值在左子树内 
		update(num, tree[root].l, l, mid);
	else//在右子树内 
		update(num, tree[root].r, mid + 1, r);
}

查询区间第k小

int query(int pre, int cur, int k, int l, int r)
{
	if(l == r)//找到第k小离散化后的值 
		return l;
	int mid = (l + r) >> 1;
	int d = tree[tree[cur].l].data - tree[tree[pre].l].data;//求[l,r]区间内左子树的值 
	if(d >= k)//若其值大于等于k,说明要找的数在左子树内 
		return query(tree[pre].l, tree[cur].l, k, l, mid);
	else//否则在右子树内,注意k - d 
		return query(tree[pre].r, tree[cur].r, k - d, mid + 1, r);
}

完整代码

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#define mem(a, b) memset(a, b, sizeof(a))
using namespace std;

const int maxn = 1e5 + 10;
struct node{
	int l, r;//左子树,右子树 
	int data;
	node()
	{
		data = 0;
	}
}tree[maxn * 20];
struct V{
	int data;
	int id;
}value[maxn];

int roots[maxn];
int ran[maxn];
int tot = 1;

bool com(V x, V y)
{
	return x.data < y.data;
}

void update(int num, int &root, int l, int r)//对root进行引用&是其精髓所在 
{
	tree[tot++] = tree[root];//新建一个节点,将其赋值为上一个根的内容 
	root = tot - 1;//将当前根赋值为新建节点 
	tree[root].data++;//更新新建的根 
	if(l == r)//找到叶子节点 
		return;
	int mid = (l + r) >> 1;
	if(mid >= num)//新插入的值在左子树内 
		update(num, tree[root].l, l, mid);
	else//在右子树内 
		update(num, tree[root].r, mid + 1, r);
}

int query(int pre, int cur, int k, int l, int r)
{
	if(l == r)//找到第k小离散化后的值 
		return l;
	int mid = (l + r) >> 1;
	int d = tree[tree[cur].l].data - tree[tree[pre].l].data;//求[l,r]区间内左子树的值 
	if(d >= k)//若其值大于等于k,说明要找的数在左子树内 
		return query(tree[pre].l, tree[cur].l, k, l, mid);
	else//否则在右子树内,注意k - d 
		return query(tree[pre].r, tree[cur].r, k - d, mid + 1, r);
}

void init()
{
	mem(ran, 0);
	mem(tree, 0);
	mem(roots, 0);
	tot = 1;
}

int main()
{
	int t, n, m;
	scanf("%d", &t);
	while(t--)
	{
		init();
		scanf("%d%d", &n, &m);
		for(int i = 1; i <= n; i++)//离散化 
		{
			scanf("%d", &value[i].data);
			value[i].id = i;
		}
		sort(value + 1, value + n + 1, com);
		for(int i = 1; i <= n; i++)
		{
			ran[value[i].id] = i;
		}
		for(int i = 1; i <= n; i++)
		{
			roots[i] = roots[i - 1];
			update(ran[i], roots[i], 1, n);
		}
		for(int i = 1; i <= m; i++)
		{
			int x, y, z;
			scanf("%d%d%d", &x, &y, &z);
			printf("%d\n", value[query(roots[x - 1], roots[y], z, 1, n)].data);//
		}
	}
	return 0;
}
发布了73 篇原创文章 · 获赞 15 · 访问量 8115

猜你喜欢

转载自blog.csdn.net/ln2037/article/details/100629156