写在前面的话
初中真的落下了好多知识啊,高中要慢慢补回来呢QAQ
概念
主席树维护的是一个前缀,相当于我们打n棵权值线段树,第i棵表示在1-i的范围内的数在离散化之后每一个数的区间出现了多少次
标准姿势
那么我们怎么样才可以求在区间[i,j]中的k小值呢?
首先因为是权值线段树,我们要离散化一波QAQ
我们设tree[i-1]表示以1–>i-1这些节点建造的一颗权值线段树,tree[j]表示以1–>j这些节点建造的一颗权值线段树,那么tree[i-1,2]就表示1–>i-1中有这么多个节点在[1,n/2]这个范围内,tree[j,2]同理
那么在[i,j]这个区间内就有tree[i,2]-tree[j-1,2]那么多个数在[1,n/2]这个范围内如果,设c=tree[i,2]-tree[j-1,2],那么如果c>=k,那么第k小的值就在[1,n/2]这个范围内,否则就在[n/2+1,n]这个范围内,然后我们两棵树同时往下走,最后就可以在log的时间内求出解了。
出现的问题
如果要建这么多棵线段树,那么在maketree的时候就已经TLE+MLE了,那么我们怎么解决这个问题呢?
解决
我们设[1,i]的权值线段树为tree0[x],[1,i+1]的权值线段树为tree1[x]。
那么如果a[i+1]这个节点是[1,n/2]中的一个值,以tree0[3]和tree1[3]为根的两颗子树是完全相同的,因为它们在[n/2+1,n]这个范围内的每一个节点的值都是完全一样的。那么我们拓展一下这个结论,就会发现tree1这颗线段树在每一层都只有一个和tree[0]值不一样的节点,也就是说tree1和tree[0]只会有log n 个不同的节点。那么实际上n棵权值线段树一共只有n log n 个不同值的节点。
我们可以设一个tree[i],其中tree[i,1]表示这个区间的左儿子在tree中的第tree[i,1]个位置,那么tree[i,2]表示的就是右儿子,tree[i,3]表示的是当前这个区间的值(好像有点难表达耶QAQ,但是自己手玩一下再看看程序就很好理解了)
那么每一次我们都只需要新建log n个节点,其他节点连到前面就可以了
给出一道不带修改的k小值例题:
戳我戳我QAQ
贴代码
var
tree:array[0..5000005,1..3]of longint;
a,id,c,root,cs:array[0..100005]of longint;
i,j,k,l,n,m,x,y,p,ss,ls:longint;
procedure qsort(l,r:longint);
var
i,j,mid:longint;
begin
i:=l;
j:=r;
mid:=a[(i+j) div 2];
repeat
while a[i]<mid do inc(i);
while a[j]>mid do dec(j);
if i<=j then
begin
a[0]:=a[i]; a[i]:=a[j]; a[j]:=a[0];
id[0]:=id[i]; id[i]:=id[j]; id[j]:=id[0];
inc(i); dec(j);
end;
until i>j;
if i<r then qsort(i,r);
if l<j then qsort(l,j);
end;
procedure maketree(var num,x:longint;l,r:longint);
var
mid:longint;
begin
tree[p]:=tree[x];
inc(tree[p,3]);
x:=p;
inc(p);
if l=r then exit;
mid:=(l+r) div 2;
if num<=mid then maketree(num,tree[x,1],l,mid)
else maketree(num,tree[x,2],mid+1,r);
end;
procedure find(i,j,k,l,r:longint);
var
mid,t:longint;
begin
if l=r then
begin
ss:=l;
exit;
end;
mid:=(l+r) div 2;
t:=tree[tree[j,1],3]-tree[tree[i,1],3];
if k<=t then find(tree[i,1],tree[j,1],k,l,mid) else
begin
k:=k-t;
find(tree[i,2],tree[j,2],k,mid+1,r);
end;
end;
begin
assign(input,'zhuxishu.in'); reset(input);
readln(n,m);
for i:=1 to n do
begin
read(a[i]);
id[i]:=i;
end;
readln;
qsort(1,n);
c[1]:=1;
cs[1]:=a[1];
cs[0]:=1;
for i:=2 to n do if a[i]=a[i-1] then c[i]:=c[i-1] else
begin
c[i]:=c[i-1]+1;
inc(cs[0]);
cs[cs[0]]:=a[i];
end;
ls:=1;
for i:=1 to n do a[id[i]]:=c[i];
p:=1;
for i:=1 to n do
begin
root[i]:=root[i-1];
maketree(a[i],root[i],1,n);
end;
for i:=1 to m do
begin
readln(x,y,k);
find(root[x-1],root[y],k,1,n);
writeln(cs[ss]);
end;
close(input);
end.