zoukankan      html  css  js  c++  java
  • 主席树

    问题

    给出n个数,q个询问,求l-r内的第k小值(n,q<=2e5)

    方法一:平衡树

    方法二:主席树

    下面来看一看主席树是怎么做的。

    主席树是一种特殊的线段树,针对这一题,我们可以对每个区间[x,y]维护一颗线段树,[l,r]表示在[x,y]区间内,数的大小在[l,r]范围内的数的个数。

    但这种思想的实现一定超过了空间与时间的限制。

    1. 考虑优化时间。考虑前缀和的思想,首先离散化数据,sum[1,i]-sum[1,j-1]即代表了区间sum[j,i],这里不妨也可以运用这种思想,只对每一个前缀进行维护。
    2. 但光有上述这点还是不够的。我们会发现,对于sum[1,i]和sum[1,i+1]这两个状态,只有一个元素的差异,所以可以考虑持久化。

    即对sum[1,i+1]的[l,r]来说,若[l,mid]和sum[1,i]是相同的,则可以直接指向sum[1,i]的[l,mid],若是不相同的,则新建一个节点。很容易发现,这样的空间复杂度是logn*n的。

    补充1:对于2,有其一定的实现技巧。

    //此处的insert指插入x节点

    procedure insert(pre,x,h,t:longint);
    var tmp,mid:longint;
    begin
    inc(now); p[now]:=p[pre]; inc(p[now].x); tmp:=now;
    if h=t then exit;
    mid:=(h+t) div 2;
    if (x<=mid ) then
    begin
    insert(p[now].h,x,h,mid);
    p[tmp].h:=tmp+1;
    end else
    begin
    insert(p[now].t,x,mid+1,t);
    p[tmp].t:=tmp+1;
    end;
    end;

     

    补充2:对于query操作,考虑左边数的个数>=sum,则往左边走,反之往右走。但此处由于前缀和操作的存在增加了一定的复杂性。其实也就是要同时统计出两个区间1-x,1-y的数值的当前范围。

    function query(x,y,h,t,sum:longint):longint;
    var tmp,mid:longint;
    begin
    if (h=t) then exit(h);
    tmp:=p[p[y].h].x-p[p[x].h].x;
    mid:=(h+t) div 2;
    if tmp<sum then exit(query(p[x].t,p[y].t,mid+1,t,sum-tmp))
    else exit(query(p[x].h,p[y].h,h,mid,sum));
    end;

     

    以上的是离线的主席树,时间复杂度是o(nlogn) 空间(nlogn);

    完整代码:

    uses math;
    type re=record
      a,b,c:longint;
    end;
    type ree=record
      x,h,t:longint;
    end;
    var
      i,j,m,n,now,l,c,d,e,tmp:longint;
      a:array[0..202222]of re;
      num,f,real:array[0..222222]of longint;
      p:array[0..10002222]of ree;
    procedure swap(var x,y:re);
    var tmp:re;
    begin
      tmp:=x; x:=y; y:=tmp;
    end;
    procedure qsort(h,t:longint);
    var i,j,mid:longint;
    begin
      i:=h; j:=t; mid:=a[(i+j) div 2].a;
      repeat
        while a[i].a<mid do inc(i);
        while a[j].a>mid do dec(j);
        if i<=j then
        begin
          swap(a[i],a[j]); inc(i); dec(j);
        end;
      until i>j;
      if i<t then qsort(i,t);
      if h<j then qsort(h,j);
    end;
    procedure build(x,h,t:longint);
    var mid:longint;
    begin
      p[x].h:=x*2; p[x].t:=x*2+1; now:=max(now,x*2+1);
      if h=t then exit;
      mid:=(h+t) div 2;
      build(x*2,h,mid); build(x*2+1,mid+1,t);
    end;
    procedure insert(pre,x,h,t:longint);
    var tmp,mid:longint;
    begin
      inc(now); p[now]:=p[pre]; inc(p[now].x); tmp:=now;
      if h=t then exit;
      mid:=(h+t) div 2;
      if (x<=mid ) then
      begin
        insert(p[now].h,x,h,mid);
        p[tmp].h:=tmp+1;
      end else
      begin
        insert(p[now].t,x,mid+1,t);
        p[tmp].t:=tmp+1;
      end;
    end;
    function query(x,y,h,t,sum:longint):longint;
    var tmp,mid:longint;
    begin
      if (h=t) then exit(h);
      tmp:=p[p[y].h].x-p[p[x].h].x;
      mid:=(h+t) div 2;
      if tmp<sum then exit(query(p[x].t,p[y].t,mid+1,t,sum-tmp))
      else exit(query(p[x].h,p[y].h,h,mid,sum));
    end;
    begin
      readln(n,m);
      for i:=1 to n do
      begin
        read(a[i].a);
        a[i].b:=i;
      end;
      qsort(1,n);
      a[0].a:=-maxlongint; l:=0;
      for i:=1 to n do
      begin
        if a[i].a<>a[i-1].a then inc(l);
        num[a[i].b]:=l;
        real[l]:=a[i].a;
      end;
      build(1,1,n);
      f[0]:=1;
      for i:=1 to n do
      begin
        f[i]:=now+1;
        insert(f[i-1],num[i],1,n);
      end;
      for i:=1 to m do
      begin
        read(c,d,e);
        writeln(real[query(f[c-1],f[d],1,n,e)]);
      end;
    end.
    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=1000000;
    struct re{int a,b;};
    struct ree{int h,t,x;};
    re a[maxn];
    ree p[maxn*20];
    int n,m,l,now,rea[maxn],num[maxn],f[maxn];
    void build(int x,int h,int t)
    {
        p[x].h=x*2; p[x].t=x*2+1; now=max(now,x*2+1);
        if (h==t) return;
        int mid=(h+t)/2;
        build(x*2,h,mid); build(x*2+1,mid+1,t); 
    };
    void insert(int pre,int x,int h,int t)
    {
        now++; p[now]=p[pre]; p[now].x++;
        if (h==t) return;
        int mid=(h+t)/2;
        if (x<=mid) p[now].h=now+1,insert(p[pre].h,x,h,mid);
        else p[now].t=now+1,insert(p[pre].t,x,mid+1,t);
    };
    int query(int x,int y,int h,int t,int sum)
    {
        if (h==t) return(h);
        int tmp=p[p[y].h].x-p[p[x].h].x;
        int mid=(h+t)/2;
        if (tmp<sum) return(query(p[x].t,p[y].t,mid+1,t,sum-tmp));
        else return(query(p[x].h,p[y].h,h,mid,sum));
    };
    bool cmp(re x,re y)
    {
         return(x.a<y.a);
    };
    int main(){
      cin>>n>>m;
      for (int i=1;i<=n;i++) cin>>a[i].a,a[i].b=i;
        sort(a+1,a+n+1,cmp);
        a[0].a=-500000000;
        for (int i=1;i<=n;i++)
        {
            if (a[i].a!=a[i-1].a) l++;
            num[a[i].b]=l;
            rea[l]=a[i].a;
      }
      build(1,1,n);
      f[0]=1;
      for (int i=1;i<=n;i++)
      {
          f[i]=now+1;
          insert(f[i-1],num[i],1,n);
      }
      for (int i=1; i<=m;i++)
      {
           int c,d,e;
           cin>>c>>d>>e;
             cout<<(rea[query(f[c-1],f[d],1,n,e)])<<endl; 
      }
    }

    总的来说,主席树是前缀和+动态开点的线段树

    接下来的是在线版的主席树,要求支持区间的修改。

    这种算法保留了主席树中的权值线段树和动态开点的思想

    同时加入了树状数组维护(因此没有了前缀和维护)

    即对于每一个节点开一颗权值线段树,查询和修改时类似树状数组的思想

    时间复杂度(nlognlogn) 空间复杂度(nlognlogn)

  • 相关阅读:
    26种激发人创造力的天使商标设计
    TopFreeTheme精选免费模板【20130703】
    解决插入到MySql数据库中乱码问题
    商务名片创意搜罗
    10个优质PSD文件资源下载
    TopFreeTheme精选免费模板【20130701.特别版】
    【原创】关于java对象需要重写equals方法,hashcode方法,toString方法 ,compareto()方法的说明
    谨慎使用keySet:对于HashMap的2种遍历方式比较
    有关google的guava工具包详细说明
    java消除 list重复值及交集,并集,差集
  • 原文地址:https://www.cnblogs.com/yinwuxiao/p/7892787.html
Copyright © 2011-2022 走看看