每日一题 2019/4/7

今天学一下主席树

最简单的就是给你一个序列,询问是给定区间中第k大的数。

 

看了这篇博客,这个图讲解的真的无敌好懂https://blog.csdn.net/bestFy/article/details/78650360

 

大概的思路就是先对数据进行离散化,主席树每一个节点都是一颗线段树,储存的信息是插入了第i个点后,主席树的状态。

插入就是对离散化后的数据,当成一颗权值线段树来加值就行了

查询就是一个巧妙的类似前缀和的东西,求一下差就好。

另外如果每一个节点都是一颗严格的线段树,空间就是n^2的,处理办法是变成一个递推的过程,如果和上一次插入的状态相等,就连上去,不等才重新开,这样空间是nlogn的。

说的很粗糙,也没指望给谁讲懂,想学去看那个博客就好。

这个算法还是挺妙的。

int a[maxn], b[maxn];
int L[20*maxn], R[20*maxn];
int T[maxn], sum[20*maxn];
int n, m, q, tot = 0;

inline int build(int l, int r){
	int rt = ++tot;
	if(l < r){
		int mid = (l + r) >> 1;
		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 = ++tot;
	L[rt] = L[pre]; R[rt] = R[pre]; sum[rt] = sum[pre] + 1; 
	if(l < r){
		int mid = (l + r) >> 1;
		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]], mid = (l + r) >> 1;
	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()
{
	int t; scanf("%d", &t);
	while(t--){
		tot = 0;
		scanf("%d %d", &n, &q);
		rep(i, 1, n) scanf("%d", a + i), b[i] = a[i];
		sort(b + 1, b + n + 1);
		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] = update(T[i-1], 1, m, a[i]);
		}
		while(q--){
			int l, r, k; scanf("%d %d %d", &l, &r, &k);
			int p = query(T[l-1], T[r], 1, m, k);
			printf("%d\n", b[p]);
		}
	}
	return 0;
}

 

你可能感兴趣的:(每日一题)