• 主席树[可持久化线段树]学习笔记


    一句话算法:

    主席树,又名可持久化线段树,是一种基于前缀和思想对历史版本进行保存,可支持单点修改,查询某个历史版本下某位置的值的数据结构。

    主要思想:

    对于我们上面加粗的命题,我们先考虑暴力的做法:

    对于每一个历史状态,建立一棵线段树维护当前状态的信息。

    然后你会发现,状态转移和空间耗损都非常巨大,(边(MLE)(TLE)的感觉怎么样)你将会得到一个(TM(LE)^2)的结果。

    我们再考虑如何加速状态转移和压缩空间。

    考虑到每一次单点修改时仅仅改动了一个点,其影响的仅仅是这个点到根的这一条链,而其他状态都是不变的:所以我们没有必要将每个节点都更新一遍,而是将被改动的节点新建,然后接到上一棵树的节点上,这样每一次状态转移被压缩成了(log(n))的复杂度,空间同理。

    例题:

    感觉对着空气还是不太好讲,所以我们来看一道题:

    给定N个整数构成的序列,将对于指定的闭区间查询其区间内的第K小值。

    提起主席树,几乎所有人最先想到的还是这样一道题。对于区间第(k)大,我们建立权值线段树(即以权值作为下标的线段树,节点权值代表数字出现次数),用上面的方法进行状态转移,第(i)棵权值线段树维护区间([1, i])的信息,每次查询时利用前缀和思想作差得到中间状态([l, r]),然后在树上进行查找,若左子树中的元素总数(geq k),说明第(k)大在左子树里,于是进入左子树递归,否则进入右子树递归,注意进入右子树递归时新的(k)要减掉左子树的(size)

    主席树的空间复杂度是(nlog(n))级别的,一般我们会直接开(n << 5)的空间不然会RE到飞起。虽然空间消耗还是很大,但是比起暴力,依然是一种优越的做法。

    题外话:

    主席树的可持久化思想在其他算法上也得到了相应的应用,如可持久化数组、可持久化Trie等。

    所以才有了那么多毒瘤数据结构

    代码:

    #include<bits/stdc++.h>
    #define N (200000 + 5)
    using namespace std;
    inline int read() {
    	int cnt = 0, f = 1; char c = getchar();
    	while (!isdigit(c)) {if (c == '-') f = -f; c = getchar();}
    	while (isdigit(c)) {cnt = (cnt << 3) + (cnt << 1) + c - '0'; c = getchar();}
    	return cnt * f;
    }
    int a[N], b[N * 2], n, m, q, rt[N], tot;
    int x, y, k, res;
    void Discretize() {
    	sort (b + 1, b + n + 1);
    	q = unique(b + 1, b + n + 1) - b - 1;
    	for (register int i = 1; i <= n; i++) 
    		a[i] = lower_bound(b + 1, b + q + 1, a[i]) - b;
    }
    struct node{
    	int l, r, sum;
    	#define l(p) tree[p].l
    	#define r(p) tree[p].r
    	#define sum(p) tree[p].sum
    }tree[N * 18];
    
    void build(int &p, int l, int r) {
    	p = ++tot;
    	if (l == r) return;
    	int mid = (l + r) >> 1;
    	build (l(p), l, mid);
    	build (r(p), mid + 1, r);
    }
    
    void insert(int &p, int l, int r, int last, int pos) {
    	p = ++tot;
    	l(p) = l(last), r(p) = r(last);
    	sum(p) = sum(last) + 1;
    	if (l == r) return;
    	int mid = (l + r) >> 1;
    	if (pos <= mid) insert(l(p), l, mid, l(last), pos);
    	else insert(r(p), mid + 1, r, r(last), pos);
    }
    
    int query (int L, int R, int l, int r, int k) {
    	if (l == r) return l;
    	int mid = (l + r) >> 1;
    	int cnt = sum(l(R)) - sum(l(L));
    	if (k <= cnt) return query(l(L), l(R), l, mid, k);
    	else return query(r(L), r(R), mid + 1, r, k - cnt);
    }
    
    int main() {
    	n = read(); m = read();
    	for (register int i = 1; i <= n; i++) a[i] = b[i] = read();
    	Discretize();
    	build(rt[0], 1, q);
    	for (register int i = 1; i <= n; ++i) insert(rt[i], 1, q, rt[i - 1], a[i]);
    	while (m--) {
    		x = read(), y = read(), k = read();
    		res = query(rt[x - 1], rt[y], 1, q, k);
    		printf("%d
    ", b[res]);
    	}
    	return 0;
    }
    
  • 相关阅读:
    struts2之拦截器
    JavaWeb开发之HttpServletResponse
    JavaWeb开发之Servlet
    HTTP协议详解
    字符串
    数组
    第一个只出现一次的字符
    DDoS的类型及原理
    引用变量&和指针*的区别
    赋值运算符的重载
  • 原文地址:https://www.cnblogs.com/kma093/p/11141175.html
Copyright © 2020-2023  润新知