主席树 从入门到入门

前言:

树剖板子早就打腻了。一直想学习一些新的模板,今天就学习一下主席树。
其实是因为暑假集训的东西我全忘了

1. luogu P3834 【模板】可持久化线段树 2(主席树)

最板子的一道题,然而看了好长时间才看明白,震惊,我竟然折磨菜,活到爆!
主席树经典用处:求静态区间第k大。
先对a数组离散化,然后对a的每一个前缀开一个权值线段树,查询[l,r]的第k大时,需要线段树上二分,分别找到[1,r]中有几个大于mid,[1,l-1]中有多少大于mid,然后相减,就是[l,r]中大于mid的个数。
代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn=200000+10;
#define gc() (p1 == p2 ? (p2 = buf + fread(p1 = buf, 1, 1 << 20, stdin), p1 == p2 ? EOF : *p1++) : *p1++)
#define read() ({ register int x = 0, f = 1; register char c = gc(); while(c < '0' || c > '9') { if (c == '-') f = -1; c = gc();} while(c >= '0' && c <= '9') x = x * 10 + (c & 15), c = gc(); f * x; })
char buf[1 << 20], *p1, *p2;
int a[maxn],b[maxn],root[maxn<<5];
struct node{
	int ls,rs,size;
}tree[maxn<<5];
int n,m,tot;
bool cmp(int x,int y){
	return x<y;
}
void update(int &u,int l,int r,int x){
	int v=++tot;
	tree[v]=tree[u];
	u=v;
	tree[v].size++;
	if(l==r) return;
	int mid=(l+r)>>1;
	if(x<=mid) update(tree[u].ls,l,mid,x);
	else update(tree[u].rs,mid+1,r,x);
}
int query(int s,int t,int k){
	s=root[s];
	t=root[t];
	int mid,sum,l=1,r=b[0];
	while(l<r){
		mid=(l+r)>>1;
		sum=tree[tree[t].ls].size-tree[tree[s].ls].size;
		if(sum>=k){
			s=tree[s].ls;
			t=tree[t].ls;
			r=mid;
		}else{
			k-=sum;
			s=tree[s].rs;
			t=tree[t].rs;
			l=mid+1;
		}
	}
	return l;
}
void Solve(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i){
		scanf("%d",&a[i]);
		b[i]=a[i];
	}
	sort(b+1,b+n+1,cmp);
	b[0]=unique(b+1,b+n+1)-b-1;
	root[0]=++tot;
	for(int i=1;i<=n;++i){
		root[i]=root[i-1];
		int x=lower_bound(b+1,b+b[0]+1,a[i])-b;
		update(root[i],1,b[0],x);
	}
	int l,r,k;
	while(m--){
		scanf("%d%d%d",&l,&r,&k);
		printf("%d
",b[query(l-1,r,k)]);
	}
}
int main(){
	Solve();
	return 0;
}


这里我在纠结一个问题,就是二分的过程。
对于普通的二分答案,我一般是这么写的:

??

while(l<=r){
	int mid=(l+r)>>1;
	if(check(mid)) r=mid-1;
	else l=mid+1;
}
return l;

但是搬到这里,他就不对了。

int query(int s,int t,int k){//这是错误代码
	s=root[s];
	t=root[t];
	int mid,sum,l=1,r=b[0];
	while(l<=r){
		mid=(l+r)>>1;
		sum=tree[tree[t].ls].size-tree[tree[s].ls].size;
		if(sum>=k){
			s=tree[s].ls;
			t=tree[t].ls;
			r=mid-1;
		}else{
			k-=sum;
			s=tree[s].rs;
			t=tree[t].rs;
			l=mid+1;
		}
	}
	return l;
}

反正我就挺迷惑。
后来张大仙给出了一个令我信服的解释。
其实这压根不是二分答案,这个while循环模拟的是线段树上二分。
换成递归版本应该是这样:

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);
}

现在就很清晰了。

原文地址:https://www.cnblogs.com/wwcdcpyscc/p/13858673.html