【算法】 口胡主席树
“可持久化”几个字限制我的想象力,导致我长久没有去接触这种强大的数据结构,主席树的思想如下:
在线段树中,不难发现,每次修改与查询的时间复杂度都是o(logn)的,这是因为每次操作所要调用与修改的节点也是logn个。
在一些题目中,题目要求我们查询线段树(尤其是值域线段树)的历史版本,这就可以用到主席树这一数据结构,每次修改都只新增logn个新节点,并记录下来每次修改后根的位置,每次查询都从某版本的根开始,就可以方便快捷的操作,同时把时间复杂度卡死在o(logn)。
题目:区间第K大
主席树的经典题,运用一点前缀和的思想。A1~A2,A1~A2......A1~An每个区间建立起一棵值域线段树。把数组中每一个数看作插入操作,从头到尾相当于建立了n个版本的主席树。每次查询的时候,一个区间内数的个数便可以用差分求出,然后用线段树常规求第K大可以了。
代码(粪山,LC原话):
#include <iostream> #include <cstdio> #include <algorithm> #include <cmath> #define maxn 100005*160 #define lc(a1) t[a1].ch[0] #define rc(a1) t[a1].ch[1] #define sum(a1) t[a1].sum #define left(a1) t[a1].left #define right(a1) t[a1].right struct hjt_tree { int ch[2],sum,left,right; }t[maxn]; int a[maxn],b[maxn],n,m,size,rt[maxn]; void change(int,int,int); int query(int,int,int); void build(int,int,int); int main() { scanf("%d%d",&n,&m); for (int i = 1;i <= n;i++) scanf("%d",&a[i]), b[i] = a[i]; std::sort(b + 1,b + n + 1); int len = std::unique(b + 1,b + n + 1) - b - 1; build(rt[0] = 0,1,n); for (int i = 1;i <= n;i++) { int loc = std::lower_bound(b + 1,b + len + 1,a[i]) - b; change(rt[i] = ++size,rt[i - 1],loc); } for (int i = 1;i <= m;i++) { int x,y,z; scanf("%d%d%d",&x,&y,&z); printf("%d\n",b[query(rt[x - 1],rt[y],z)]); } return 0; } void build(int x,int l,int r) { sum(x) = 0; left(x) = l; right(x) = r; if (l == r) return ; int mid = (l + r) / 2; build(lc(x) = ++size,l,mid); build(rc(x) = ++size,mid + 1,r); } void change(int x,int ori,int loc) { sum(x) = sum(ori) + 1; lc(x) = lc(ori); rc(x) = rc(ori); left(x) = left(ori); right(x) = right(ori); if (left(x) == right(x)) return ; int mid = (left(x) + right(x)) / 2; if (loc <= mid) change(lc(x) = ++size,lc(ori),loc); else change(rc(x) = ++size,rc(ori),loc); } int query(int x,int y,int k) { int know = sum(lc(y)) - sum(lc(x)); if (left(x) == right(x)) return left(x); int mid = (left(x) + right(x)) / 2; if (k <= know) return query(lc(x),lc(y),k); else return query(rc(x),rc(y),k-know); }