[BZOJ2653] middle - 主席树(可持久化线段树) - 二分

2653: middle

Time Limit: 20 Sec   Memory Limit: 512 MB
Submit: 1046   Solved: 599
[ Submit][ Status][ Discuss]

Description

  一个长度为n的序列a,设其排过序之后为b,其中位数定义为b[n/2],其中a,b从0开始标号,除法取下整。
  给你一个长度为n的序列s。
  回答Q个这样的询问:s的左端点在[a,b]之间,右端点在[c,d]之间的子序列中,最大的中位数。
  其中a<b<c<d。
  位置也从0开始标号。
  我会使用一些方式强制你在线。

Input

  第一行序列长度n。
  接下来n行按顺序给出a中的数。
  接下来一行Q。
  然后Q行每行a,b,c,d,我们令上个询问的答案是x(如果这是第一个询问则x=0)。
  令数组q={(a+x)%n,(b+x)%n,(c+x)%n,(d+x)%n}。
  将q从小到大排序之后,令真正的要询问的a=q[0],b=q[1],c=q[2],d=q[3]。
  输入保证满足条件。

Output

  Q行依次给出询问的答案。

Sample Input

5
170337785
271451044
22430280
969056313
206452321
3
3 1 0 2
2 3 1 4
3 1 4 0

271451044
271451044
969056313

Sample Output

HINT

  0:n,Q<=100

  1,...,5:n<=2000

  0,...,19:n<=20000,Q<=25000


Source

首先我们将数组排序,然后一个一个存进去建主席树……因为我们要用线段树来维护求最大子段和,需要维护sum,lmax和rmax。但是我们不可能建n棵线段树,否则空间爆炸。 在这里,我们对每棵线段树,用1表示在排序前的这个位置的数比自身大或等于自身,-1代表在排序前的这个位置的数比自身小。
然后我们会发现,如果我们按照排序的模式从小到大建树,每次只需要修改一个节点。也就是说,我们对每棵线段树,可以借用前一棵的deep=log n个结点,而自己新建的结点也只需要deep个。(如果修改的点在左子树,那么右子树只需使用上一次的同一位置的右子树即可。)
-------------------------分割线-------------------------
    在这里稍微解释一下主席树,也就是可持久化线段树。
    比如我们现在要维护 31 77 40 84 70 109 70 80 43 40这样一段区间,以期求得区间的第k大值,在不使用划分树的情况下,首先我们可以将这些数离散化,就变成了1 5 2 7 4 8 4 6 3 2
    然后我们就可以建线段树了。在这里,我们记录的是这段区间中每个数出现的次数,从1~1,1~2,1~3,1~4...1~n(10)分别建树,每棵树的大小都是n。
    那么我们第一棵树的结点就是这样的:( “{}”代表子树,"[]"代表区间,*代表值)
    [root]{ [1~5] { [1~2]{ *1,*0 },[3~5] { *0, [4~5]{ *0, *0} } }, [6~10] { [6~7]{ *0,*0 },[8~10] { *0, [9~10]{ *0, *0} } }
    第二棵树的结点是这样的
    [root]{ [1~5] { [1~2]{ *1,*0 },[3~5] { *0, [4~5]{ *0, *1} } }, [6~10] { [6~7]{ *0,*0 },[8~10] { *0, [9~10]{ *0, *0} } }
    那么,为了节约空间,我们观察这棵线段树,发现上面有色的部分都是和原来的线段树完全一样的! 所以,我们完全可以从原来的线段树里借用。
    同理,第三棵树是这样的:
    [root]{ [1~5] { [1~2]{ *1,*1 },[3~5] { *0, [4~5]{ *0, *1} } }, [6~10] { [6~7]{ *0,*0 },[8~10] { *0, [9~10]{ *0, *0} } }
    所以我们就把每棵线段树的平均空间压缩到了O(log n)
    然后解决了空间的问题,我们就发现,这个线段树是可以相加减的。也就是说,我们求u~v的区间,实际上就是seg[v]-seg[u-1]所表示的值!
    这样一来,解决第k大的数的问题也很简单了。
-------------------------分割线-------------------------
    建好了主席树之后,剩下来的就好办了:求左结点在[a,b]间而右节点在[c,d]间的最大子段和完全可以将[b+1,c-1]的子段和加上[a,b]的最大右子段和和[c,d]的最大左子段和。 我们二分一个数k,判断它是否有能力成为这个区间的中位数或比中位数小。显然地,当这个最大子段和>=0时,这个数一定小于或等于中位数。我们可以发现,满足条件的最大的数便是这个区间的中位数,因为但凡比它大的数子段和都要-=2
    唔……码了这么多字……
#include "stdio.h"
#include "iostream"
#include "stdlib.h"
#include "algorithm"
using namespace std;
 
const int N=20005,Lg=17;
 
struct node {
  int l,r; int val;
  int lmx,rmx,sum;
  node * s[2];
  inline void ins(int _l,int _r,node* lson,node* rson){
    l=_l; r=_r; s[0]=lson; s[1]=rson;
  }
  inline void make (int v){ val=lmx=rmx=sum=v;}
} ptree[Lg*N]; //表示主席树的点集 
 
node* root[N]; //N个版本的根结点所在位置 
int readin[N],sorted[N],n,tmp;
//分别表示排序和没排序的 
int past[N]; //原来所在的位置 
 
inline void push_up(int v){
  node * c=ptree+v;
  c->sum=c->s[0]->sum+c->s[1]->sum;
  c->lmx=max(c->s[0]->lmx,c->s[0]->sum+c->s[1]->lmx);
  c->rmx=max(c->s[1]->rmx,c->s[1]->sum+c->s[0]->rmx);
}
 
node* Build (int l,int r){
  int k=++tmp;
  ptree[k].l=l;
  ptree[k].r=r;
  if (l==r) {
    ptree[k].make(1);
    return ptree+k;
  }
  int mid=(l+r)>>1;
  ptree[k].ins(l,r,Build(l,mid),Build(mid+1,r));
  push_up(k);
  return ptree+k;
}
 
node* build_next (node *last,int l,int r,int edit){
  int k=++tmp;
  ptree[k].l=l;
  ptree[k].r=r;
  if (l==r) {
    ptree[k].make(-1);
    return ptree+k;
  }
  int mid=(l+r)>>1;
  if (edit<=mid)
    ptree[k].ins
     (l,r,build_next(last->s[0],l,mid,edit),last->s[1]);
  else
    ptree[k].ins
     (l,r,last->s[0],build_next(last->s[1],mid+1,r,edit));
  push_up(k);
  return ptree+k;
}
 
void init(){
  scanf("%d",&n); int i;
  for (i=1;i<=n;i++)
    scanf("%d",&readin[i]);
   
  for (i=1;i<=n;i++)
    sorted[i]=readin[i];
  sort(sorted+1,sorted+n+1);
  for (i=1;i<=n;i++){
    int bot=1,top=n,mid=(1+n)>>1;
    while (bot<top){
      if (sorted[mid]==readin[i]) break;
      if (sorted[mid]>readin[i]) top=mid-1;
      else bot=mid+1;
      mid=(top+bot)>>1;
    }
    past[mid]=i;
  }
  root[1]= Build (1,n);
  for (i=2;i<=n;i++)
    root[i]= build_next (root[i-1],1,n,past[i-1]);
}
 
int query_sum (node *srt,int l,int r){
  int _l=srt->l,_r=srt->r,mid=(_l+_r)>>1;
  if(_l==_r||(_l==l&&_r==r)) return srt->sum;
  if (r<=mid) return query_sum(srt->s[0],l,r);
  if (l>mid) return query_sum(srt->s[1],l,r);
  return query_sum(srt->s[0],l,mid)+
         query_sum(srt->s[1],mid+1,r);
}
 
int query_lmx (node *srt,int l,int r){
  int _l=srt->l,_r=srt->r,mid=(_l+_r)>>1;
  if(_l==_r||(_l==l&&_r==r)) return srt->lmx;
  if (r<=mid) return query_lmx(srt->s[0],l,r);
  if (l>mid) return query_lmx(srt->s[1],l,r);
  int a=query_lmx(srt->s[0],l,mid);
  a=max (a,query_sum(srt->s[0],l,mid)+
          query_lmx(srt->s[1],mid+1,r));
  return a;
}
 
int query_rmx (node *srt,int l,int r){
  int _l=srt->l,_r=srt->r,mid=(_l+_r)>>1;
  if(_l==_r||(_l==l&&_r==r)) return srt->rmx;
  if (r<=mid) return query_rmx(srt->s[0],l,r);
  if (l>mid) return query_rmx(srt->s[1],l,r);
  int a=query_rmx(srt->s[1],mid+1,r);
  a=max (a,query_sum(srt->s[1],mid+1,r)+
          query_rmx(srt->s[0],l,mid));
  return a;
}
 
bool check(int al,int ar,int bl,int br,int k){
  //第k版本的线段树的al~br的最大子段和 且必须包含ar~bl段 
  int x;
  if (ar+1<bl) x = query_sum(root[k],ar+1,bl-1);
  else x = 0;
  x+= query_rmx(root[k],al,ar);
  x+= query_lmx(root[k],bl,br);
  return x>=0;
}
 
int query(int al,int ar,int bl,int br){
  int bot=1,top=n,mid=(1+n)>>1;
  while (bot<top){
    if (check(al,ar,bl,br,mid))
      bot=mid;
    else
      top=mid-1;
    mid=(top+bot+1)>>1;
  }
  return sorted[mid];
}
 
void work(){
  int q,i,x=0; scanf("%d",&q);
  int t[5],j;
  for (i=1;i<=q;i++){
    scanf("%d%d%d%d",t,t+1,t+2,t+3);
    for (j=0;j<4;j++) t[j]=(t[j]+x)%n+1;
    sort(t,t+4);
    printf("%d\n",x=query(t[0],t[1],t[2],t[3]));
  }
}
 
int main(){
  init(); 
  work();
  return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值