zoukankan      html  css  js  c++  java
  • BZOJ2653: middle

    2653: middle

    Time Limit: 20 Sec  Memory Limit: 512 MB
    Submit: 3027  Solved: 1731
    [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]。  
    输入保证满足条件。
    第一行所谓“排过序”指的是从小到大排序!
    n<=20000,Q<=25000
     

    Output

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

    Sample Input

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

    Sample Output

    271451044
    271451044
    969056313

    HINT

     

    Source

    思路{

      记得原来做过一道$TJOI$的题目也与数列的部分排序有关,

      这种题的一般套路是二分一个答案然后把这个数列根据二分的那个值简化成一个$01$列就很容易利用数据结构维护所需要的信息。

      对于这道题,每一问的答案明显满足二分性质。

      我们首先二分一个答案x。我们先考虑中位数对于一个简化串的性质。

      发现如果$≥$中位数的数变成$1$,$<$中位数的数变成$-1$,那么数列和就一定是$0$或者$1$;

      那么我们再做推广,对于某一个特定的数$num$,按照上述方法将这个数列简化,

      数列和小于$0$时,说明比它小的数超过了一半,中位数要更小,数列和大于等于$0$时,说明比它大的数超过了一般,中位数要更大。

      这就是我们二分的$check$函数的策略。

      我们先考虑单次$check$现在我们不知道区间的端点,只知道它肯定包括了一个$[b+1,c-1]$的区间,

      于是我们直接用线段树查这一段的区间和记为$sum1$,对于前后的给定区间分别记后缀和$x$和前缀和$y$;

      现在我们只需要比较$x+sum1+y$与$0$的关系了,我们做如下分类讨论:

    • $x_{max}+sum1+y_{max}<0$:此时肯定不存在满足的区间,所以$r=mid$;
    • $x_{min}+sum1+y_{min}>0$:此时所有的给定区间均满足,所以$l=mid$;
    • $x_{max}+sum1+y_{max}>0,x_{min}+sum1+y_{min}<0$同时成立,同时我们发现:$x+sum1+y$一定能够取到$x_{max}+sum1+y_{max}$与$x_{min}+sum1+y_{min}>0$中的所有数,那么就一定能够取到$0$,即存在某一个区间中的数以此为中位数,符合题意,$l=mid$;

      后两种情况可以合并,那么我们只需要考虑第一种情况是否成立即可,因此我们可以用线段树来维护区间的前缀最大值和后缀最大值了。

      处理完这个问题,我们发现还有一个关键的问题没有解决:那就是对于每一个二分出来的$mid$,怎么快速地构建出简化数列呢?

      这就需要可持久化了,我们假设按照数列中的每一个数的值$a_i$构建简化数列并建树。

      我们发现,将$a_i$排序后的相邻两项对应的线段树实际上只有有限位置上的改变(从$a_i$到$a_{i+1}$只是将$a_i$所在位置上的值变成了$-1$),

      也就是说改变了有限条链,于是我们就可以用主席树的思想建树。

      按照排序后的值从小到大建树,每次修改某个位置上的值只增加一条链,且一个位置只会被修改一次。

      这样,当我们$check$的时候,只需要查询$mid$所对应的这棵线段树上的信息$x_{max}+sum1+y_{max}$就好了。

      建树的复杂是$O(nlogn)$(新建初始的线段树和修改$n$个位置上的数也就是增加$n$条长度为$logn$的链)

      查询的复杂度则是$O(log^2n)$。

      从而很完美地解决了这个问题。

    }

    #include<bits/stdc++.h>
    #define ll long long
    #define db double
    #define N 20010
    #define mid ((l+r)>>1)
    using namespace std;
    vector<int>que[N];
    int n,a[N],sub[N],idn,sz,bl[N];
    struct node{
      int sum,lmax,rmax;
    }tr[N*20];
    int ls[N*20],rs[N*20],rt[N];
    node  up(node a,node b){
      return (node){a.sum+b.sum,max(a.sum+b.lmax,a.lmax),max(a.rmax+b.sum,b.rmax)};
    }
    void build(int &x,int l,int r){
      x=++idn;
      if(l==r){tr[x]=(node){1,1,1};return;}
      build(rs[x],mid+1,r);
      build(ls[x],l,mid);
      tr[x]=up(tr[ls[x]],tr[rs[x]]);
    }
    void modify(int y,int &x,int k,int l,int r){
      x=++idn;tr[x]=tr[y];ls[x]=ls[y],rs[x]=rs[y];
      if(l==r){
        tr[x]=(node){-1,-1,-1};
        return ;
      }
      if(k>mid)modify(rs[y],rs[x],k,mid+1,r);
      else modify(ls[y],ls[x],k,l,mid);
      tr[x]=up(tr[ls[x]],tr[rs[x]]);
    }
    void build_tree(){
      build(rt[++rt[0]],1,n);bl[1]=1;
      for(int i=1;i<sz;++i){
        for(int j=0;j<que[i].size();++j)
          modify(rt[rt[0]],rt[rt[0]+1],que[i][j],1,n),bl[i+1]=++rt[0];
      }
    }
    int querysum(int x,int l,int r,int L,int R){
      if(L>R)return 0;
      if(l>=L&&r<=R)return tr[x].sum;
      if(mid<L)return querysum(rs[x],mid+1,r,L,R);
      if(mid>=R)return querysum(ls[x],l,mid,L,R);
      return querysum(rs[x],mid+1,r,mid+1,R)+querysum(ls[x],l,mid,L,mid);
    }
    int querylmax(int x,int l,int r,int L,int R){
      if(l==L&&r==R)return tr[x].lmax;
      if(mid>=R)return querylmax(ls[x],l,mid,L,R);
      if(mid<L)return querylmax(rs[x],mid+1,r,L,R);
      return max(querylmax(ls[x],l,mid,L,mid),querysum(ls[x],l,mid,L,mid)+querylmax(rs[x],mid+1,r,mid+1,R));
    }
    int queryrmax(int x,int l,int r,int L,int R){
      if(l==L&&r==R)return tr[x].rmax;
      if(mid>=R)return queryrmax(ls[x],l,mid,L,R);
      if(mid<L)return queryrmax(rs[x],mid+1,r,L,R);
      return max(queryrmax(rs[x],mid+1,r,mid+1,R),querysum(rs[x],mid+1,r,mid+1,R)+queryrmax(ls[x],l,mid,L,mid));
    }
    int q[5];
    bool check(int m){
      return (querysum(rt[bl[m]],1,n,q[2]+1,q[3]-1)+
    	  queryrmax(rt[bl[m]],1,n,q[1],q[2])+
    	  querylmax(rt[bl[m]],1,n,q[3],q[4]))>=0;
    }
    int main(){
      freopen("1.in","r",stdin);
      scanf("%d",&n);
      for(int i=1;i<=n;++i)scanf("%d",a+i),sub[++sub[0]]=a[i];
      sort(sub+1,sub+sub[0]+1);
      sz=unique(sub+1,sub+sub[0]+1)-sub-1;
      for(int i=1;i<=n;++i)
        a[i]=lower_bound(sub+1,sub+sz+1,a[i])-sub,que[a[i]].push_back(i);
      build_tree();
      int T;scanf("%d",&T);
      int ans=0;
      while(T--){
        for(int i=1;i<=4;++i)scanf("%d",q+i),q[i]=(q[i]+ans)%n+1;
        sort(q+1,q+5);
        int l=1,r=sz;
        while(l<=r){
          if(check(mid))l=mid+1;
          else r=mid-1;
        }
        ans=sub[(l+r)>>1];
        printf("%d
    ",ans);
      }
      return 0;
    }
    

      

  • 相关阅读:
    openVolumeMesh example 程序学习
    使用字符串创建java 对象
    HDU-1501-Zipper
    UVA-10285-Longest Run on a Snowboard
    HDU-2182-Frog
    HDU-2044-一只小蜜蜂
    POJ-1163-The Triangle
    HDU-1159-Common Subsequence
    HDU-2069-Coin Change
    HDU-4864-Task
  • 原文地址:https://www.cnblogs.com/zzmmm/p/12292144.html
Copyright © 2011-2022 走看看