数据结构——静态主席树(主席树第一部分)

定义

可持久化线段树。据说是某大佬学不会划分树,所以自己研究出了这一更为高效的算法(不愧是大佬,%%%)。由于主席树博大精深,这里分成几种类型的主席树分别讨论(无修改,维护修改,这篇是静态不修改主席树)。

作用

查询区间内某个数出现次数、查找区间第 k k k大值。

原理

学主席树,首先要学会权值线段树。权值线段树的概念比较基础。我给你一个数组 a a a,你统计出这个数组里有那些数,排个序,再另开一个数组 b b b储存这些数各自的出现次数,然后对数组 b b b建立线段树维护和。这样子一来就得到了范围内里的数出现的总次数。我们要找第 k k k大的数,只需要从根节点开始,将 k k k与左子节点的值进行比较,如果 k k k大于左子节点的值,说明第 k k k大的值在左子树囊括的区间内,反之在右子树囊括的区间内,且为第( k k k-左子树的值)大。
知道了这个后,就是主席树了,主席树要维护的是不确定区间的第 k k k大,有了上面的思路我们自然可以想到,能不能每次都建立一颗权值线段树,然后查询呢?这其实就是主席树的基本思想。只不过,直接这么来肯定TLE,这就需要优化,那么怎么优化呢?我们注意到,对于两个维护 ( 1 , a ) 和 ( 1 , b ) ( a &lt; b ) (1,a)和(1,b)(a &lt; b) (1a)(1,b)(a<b)的区间,如果这两棵线段树形态与根节点完全相同,那我们把每个节点的对应值相减就能得到维护这一段区间的线段树。那么我们是不是可以对每个节点都建一颗 ( 1 , n ) (1,n) (1n)的线段树呢?可是这样会MLE,那么怎么办呢?
我们注意到,每次后一个节点的权值线段树相对于前一个节点,只增加了一个叶子节点的值,相当于只是修改了一个从根到叶子的路,那么我们能不能保证只修改这条路,剩下的全都利用上一个根节点的值?用结构体来简单描述一下这个思想

struct node{
	int val;
	node * lchild;
	node * rchild;
} rt[maxn];
int num[maxn];

void init()
{
	//假定rt[0]已经建好(全部为0,有m个叶子结点的树)
	for(int i = 1; i <= n; i++){
		int l = 1, r = m;
		rt[i].val = rt[i-1].val +1;
		node pos = rt[i], pre = rt[i-1];
		while(l != r){
			int mid = (l+r) >> 1;
			node tmp;
			if(a[i] <= mid){
				r = mid;
				pos.rchild = pre.rchild;
				tmp.val = pre.lchild.val + 1;
				pos.lchild = tmp;
				pos = pos.lchild, pre = pre.lchild;
			}
			else{
				l = mid + 1;
				pos.lchild = pre.lchild;
				tmp.val = pre.rchild.val + 1;
				pos.rchild = tmp;
				pos = pos.rchild, pre = pre.rchild;
			}
		}
	}
}

这就是修改一条链的方法,最后的查询操作见模板。
总的来说,静态主席树大致分为四个部分:离散化、建树、赋值、查询。
以下是模板:

int c[maxn*30], ls[maxn*30], rs[maxn*30], T[maxn];
int a[maxn], b[maxn];
int m, n, q, cnt;

void init()
{
	cnt = -1;
	for(int i = 1; i <= n; i++)
		b[i] = a[i];
	sort(b+1, b+1+n);
	m = unique(b+1, b+1+n) - b -1;
}

int build(int l, int r)
{
	c[++cnt] = 0;
	int rt = cnt;
	if(l != r){
		int mid = (l+r) >> 1;
		ls[rt] = build(l, mid);
		rs[rt] = build(mid+1, r);
	}
	return rt;
}

int update(int rt, int pos, int val)
{
	c[++cnt] = c[rt] + val;
	int res, nrt;
	res = nrt = cnt;
	int l = 1, r = m;
	while(l != r){
		int mid = (l + r) >> 1;
		if(pos <= mid){
			ls[nrt] = ++cnt, rs[nrt] = rs[rt];
			nrt = ls[nrt], rt = ls[rt];
			r = mid;
		}
		else{
			ls[nrt] = ls[rt], rs[nrt] = ++cnt;
			nrt = rs[nrt], rt = rs[rt];
			l = mid+1;
		}
		c[nrt] = c[rt] + val;
	}
	return res;
}

int query(int lrt, int rrt, int k)
{
	int l = 1, r = m;
	while(l != r){
		int mid = (l+r) >> 1;
		if(c[ls[lrt]] - c[ls[rrt]] >= k){
			r = mid;
			lrt = ls[lrt], rrt = ls[rrt];
		}
		else{
			k -= c[ls[lrt]] - c[ls[rrt]];
			l = mid+1;
			lrt = rs[lrt], rrt = rs[rrt];
		}
	} 
	return b[l];
}

例题

POJ2104 K-th Number

题目分析

这题其实是一条非常裸的静态主席树题,只需要套板子就行(主要是因为实在找不到什么其他的例题。。。)
代码:

#include <cstdio>
#include <algorithm>
#include <iostream>
using namespace std;
const int maxn = 1e5 + 10;
int c[maxn*30], ls[maxn*30], rs[maxn*30], T[maxn];
int a[maxn], b[maxn];
int m, n, q, cnt;
void init()
{
	cnt = -1;
	for(int i = 1; i <= n; i++)
		b[i] = a[i];
	sort(b+1, b+1+n);
	m = unique(b+1, b+1+n) - b -1;
}
int build(int l, int r)
{
	c[++cnt] = 0;
	int rt = cnt;
	if(l != r){
		int mid = (l+r) >> 1;
		ls[rt] = build(l, mid);
		rs[rt] = build(mid+1, r);
	}
	return rt;
}
int update(int rt, int pos, int val)
{
	c[++cnt] = c[rt] + val;
	int res, nrt;
	res = nrt = cnt;
	int l = 1, r = m;
	while(l != r){
		int mid = (l + r) >> 1;
		if(pos <= mid){
			ls[nrt] = ++cnt, rs[nrt] = rs[rt];
			nrt = ls[nrt], rt = ls[rt];
			r = mid;
		}
		else{
			ls[nrt] = ls[rt], rs[nrt] = ++cnt;
			nrt = rs[nrt], rt = rs[rt];
			l = mid+1;
		}
		c[nrt] = c[rt] + val;
	}
	return res;
}
int query(int lrt, int rrt, int k)
{
	int l = 1, r = m;
	while(l != r){
		int mid = (l+r) >> 1;
		if(c[ls[lrt]] - c[ls[rrt]] >= k){
			r = mid;
			lrt = ls[lrt], rrt = ls[rrt];
		}
		else{
			k -= c[ls[lrt]] - c[ls[rrt]];
			l = mid+1;
			lrt = rs[lrt], rrt = rs[rrt];
		}
	} 
	return b[l];
}
int main()
{
	scanf("%d%d", &n, &q);
	for(int i = 1; i <= n; i++)
		scanf("%d", &a[i]);
	init();
	T[n+1] = build(1, m);
	for(int i = n; i; i--){
		int pos = lower_bound(b+1, b+1+m, a[i]) - b;
		T[i] = update(T[i+1], pos, 1);
	}
	for(int i = 1; i <= q; i++){
		int l, r, k;
		scanf("%d%d%d", &l, &r, &k);
		printf("%d\n", query(T[l], T[r+1], k));
	}
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值