poj2104_静态的主席树

写在前面的话

初中真的落下了好多知识啊,高中要慢慢补回来呢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.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值