主席树(可持久化线段树 )
给一个长为n的序列,m次询问,每次询问[l, r]内第k大的数是几。 n <= 100000, m <= 5000
比如有一个数组n个数据,那么这里记录的是从左往右,每个数据对应的线段树。最后的效果是两个数据的线段树对应值的差值。
而每个数据点(排序去重后所在坐标位置)对一个的线段树记录的是当前对应区间的个数sum(i) (i是区间标号)
举个栗子:
7 1
1 5 2 6 3 7 4
2 5 3
ans=5
update是将这个数据所在的数组位置(不是真值的数据大小排序,所以这里需要一个转换操作)向上走的每个位置sum都+1
用一个新数组记录原数组数据从小到大无重复的数据,然后遍历原数组的时就只要lowerbound查找此数据值对应的位置即可
查询区间[x,y]第k大数的操作为:
1.先抓取x-1与y这两点,两者对应线段树值dt=sum(y)-sum(x-1)作差
2.若k>dt,则说明对应值在右子树中;反之,若k<=dt,则说明在左子树中。(此环节用递归实现即可)
上代码:
const int N = 200010; int n, q, m, cnt = 0; int a[N], b[N], T[N]; int sum[N<<5], L[N<<5], R[N<<5]; inline int build(int l, int r) { int rt = ++ cnt; sum[rt] = 0; if (l < r){ L[rt] = build(l, mid); R[rt] = build(mid+1, r); } return rt; } inline int update(int pre, int l, int r, int x) { int rt = ++ cnt; L[rt] = L[pre]; R[rt] = R[pre]; sum[rt] = sum[pre]+1; if (l < r){ if (x <= mid) L[rt] = update(L[pre], l, mid, x); else R[rt] = update(R[pre], mid+1, r, x); } return rt; } inline int query(int u, int v, int l, int r, int k) { if (l >= r) return l; int x = sum[L[v]] - sum[L[u]]; if (x >= k) return query(L[u], L[v], l, mid, k); else return query(R[u], R[v], mid+1, r, k-x); } int main() { scanf("%d%d", &n, &q); for (int i = 1; i <= n; i ++){ scanf("%d", &a[i]); b[i] = a[i]; } sort(b+1, b+1+n); m = unique(b+1, b+1+n)-b-1; T[0] = build(1, m); for (int i = 1; i <= n; i ++){ int t = lower_bound(b+1, b+1+m, a[i])-b; T[i] = update(T[i-1], 1, m, t); } while (q --){ int x, y, z; scanf("%d%d%d", &x, &y, &z); int t = query(T[x-1], T[y], 1, m, z); printf("%d\n", b[t]); }