zoukankan      html  css  js  c++  java
  • 主席树

    主席树

    首先考虑一个比较经典的问题,你有一个静态的数列,每次询问一段区间 (l o r) 内的第 (k) 小。

    做法的一句话介绍,巨的人就不要往下翻了。

    对于原序列的每个前缀维护一颗线段树,维护这个区间,并且这些线段树满足可减性。

    接下具体解释下,

    考虑一个静态区间上维护区间信息,一般会想到什么呢?

    还记得我们在学线段树的时候曾经是需要维护一个可修改的区间。

    我们一共三种做法。

    第一种,直接用 (a[]) 记录,每次修改 (O(1)),查询 (O(n))

    第二种,通过差分的思想维护前缀和数组 (sum[]) 每次修改 (O(n)) ,查询 (O(1))

    第三种,则是线段树,查询,修改都是 (O(log_n)) 的。

    那么这个问题只需要维护一段静态区间,那么就用差分的思想。

    对于每个前缀都开一颗权值线段树,那么这样的话查询就变得十分的方便。

    因为只要判断左边的大小是否大于 (k) 。最裸的线段树问题。

    看似很简单,但是空间显然会炸。

    你这么做的空间复杂度是 (n * n << 2) 的。

    你不炸谁炸?

    那么这个时候就可以用到动态开点了,不知道动态开点的可以去看我的 (blog)

    这也是主席树的灵魂部分。

    记录每个前缀的根,从根开始向前一颗线段树的子树连边。

    具体的话是这样实现的。

    (a[]) 是原数列,现在处理到 (x) 节点。

    你就依次判断 (mid)(a[x]) 之间的关系。

    如果 (mid) 大于 (a[x]) 你就把第 (x) 颗线段树当前节点的右儿子连到第 (x - 1) 颗线段树对应的位置。

    否则就把第 (x) 颗线段树当前节点的左儿子连到第 (x - 1) 颗线段树对应的位置。

    至于另一边就不断递归就行了,最后只需要新建一个节点就行了。

    模板

    代码

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #include <cstring>
    #include <string>
    #include <queue>
    #define N 200020
    #define ls x << 1
    #define rs x << 1 | 1
    #define inf 0x3f3f3f3f
    #define inc(i) (++ (i))
    #define dec(i) (-- (i))
    #define mid ((l + r) >> 1)
    #define int long long
    #define XRZ 1000000003
    #define debug() puts("XRZ TXDY");
    #define mem(i, x) memset(i, x, sizeof(i));
    #define Next(i, u) for(register int i = head[u]; i ; i = e[i].nxt)
    #define file(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout);
    #define Rep(i, a, b) for(register int i = (a) , i##Limit = (b) ; i <= i##Limit ; inc(i))
    #define Dep(i, a, b) for(register int i = (a) , i##Limit = (b) ; i >= i##Limit ; dec(i))
    int dx[10] = {1, -1, 0, 0};
    int dy[10] = {0, 0, 1, -1};
    using namespace std;
    inline int read() {
        register int x = 0, f = 1; register char c = getchar();
        while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
        while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
        return x * f;
    }
    int n, m, T[N << 5], Data[N << 5], Left[N << 5], Right[N << 5], a[N], b[N], rt;
    int Build(int l, int r) {
    	int now = inc(rt);
    	if(l < r) Left[now] = Build(l, mid), Right[now] = Build(mid + 1, r);
    	return now;
    }
    int Updata(int pre, int l, int r, int x) {
    	int now = inc(rt);
    	Left[now] = Left[pre]; Right[now] = Right[pre]; Data[now] = Data[pre] + 1;
    	if(l >= r) return now;
        if(x <= mid) Left[now] = Updata(Left[pre], l, mid, x);
    	else Right[now] = Updata(Right[pre], mid + 1, r, x);
    	return now;
    }
    int Query(int x, int y, int l, int r, int k) {
    	if(l == r) return l;
    	int std = Data[Left[y]] - Data[Left[x]];
    	if(std >= k) return Query(Left[x], Left[y], l, mid, k);
    	else return Query(Right[x], Right[y], mid + 1, r, k - std);
    }
    signed main() {
    	int n = read(), m = read();
    	Rep(i, 1, n) a[i] = b[i] = read();
    	sort(b + 1, b + n + 1);
    	int M = unique(b + 1, b + n + 1) - b - 1;
    	T[0] = Build(1, M);
    	Rep(i, 1, n) {
    		a[i] = lower_bound(b + 1, b + M + 1, a[i]) - b;
    		T[i] = Updata(T[i - 1], 1, M, a[i]);
    	}
    	Rep(i, 1, m) {
    		int x = read(), y = read(), z = read();
    		printf("%d
    ", b[Query(T[x - 1], T[y], 1, M, z)]);
    	}
    	return 0;
    }
    
  • 相关阅读:
    leetcode 1 Two sum
    hdu1099
    hdu1098
    函数执行顺序
    浏览器滚动条
    2048的制作
    JavaScript--对象-检查一个对象是否是数组
    JavaScript--格式化当前时间
    JavaScript--模拟验证码
    JavaScript--模拟网络爬虫
  • 原文地址:https://www.cnblogs.com/Flash-plus/p/13834060.html
Copyright © 2011-2022 走看看