BZOJ3110 ZJOI2013 K大数查询

3110: [Zjoi2013]K大数查询

Time Limit: 20 Sec  Memory Limit: 512 MB

Description

有N个位置,M个操作。
操作有两种。
每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c
如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少。

Input

第一行N,M
接下来M行,每行形如1 a b c或2 a b c

Output

输出每个询问的结果

Sample Input

2 5
1 1 2 1
1 1 2 2
2 1 1 2
2 1 1 1
2 1 2 3

Sample Output

1
2
1

HINT

【样例说明】
第一个操作后位置1的数只有1,位置2的数也只有1。
第二个操作后位置1的数有1、2,位置2的数也有1、2。
第三次询问位置1到位置1第2大的数是1。
第四次询问位置1到位置1第1大的数是2。
第五次询问位置1到位置2第3大的数是1。‍
N,M<=50000,a<=b<=N
1操作中abs(c)<=N
2操作中c<=Maxlongint

  为什么BZOJ的排版这么鬼里鬼气。

  一道整体二分的入门题?反正我是做了蛮久的。

  都说了是整体二分了,那就分一下吧。

  自我感觉,整体二分是这么一个东西:

    二分答案都会,对于每组询问,二分答案再check看是否满足。

    那么整体二分,就是对所有询问加操作一起二分。

    二分的同样是答案,然后询问就可以分成两部分了。

    同时把操作也分成两部分。

    一部分是对前面询问要产生贡献的,一部分是已经计算完对后面的询问贡献的(不会改变/不会影响的)。

    然后两部分递归求解。

  以区间k大为例,若有多个询问,可以整体二分。

  把比二分答案mid大的数加1,check就看区间和是否大于k。

  递归求解,直到答案l==r为止。

  所以整体二分大概就长成这样:

  solve(optl,optr,l,r){

    if(optl>optr)return;

    if(l==r){

      for(int i=optl;i<=optr;++i)

        if(opt[i]是询问)

          Ans[opt[i].id]=l;

      return;

    }

    ……

    solve(optl,optl+tot1-1,l,mid);

    solve(optl+tot1,optr,mid+1,r);

  }

  中间判断一般就是树状数组?线段树?差分?……

  按照XHR犇所说,整体二分复杂度要和操作序列的长度线性相关。

  也就是说当前长度是len那么复杂度就要和len线性相关,不然破坏复杂度。

  回头来看这道题。

  答案明显是满足可二分性的。那么试着整体二分。

  按照套路,solve(optl,optr,l,r)。

  搞出一个mid,来看看怎么check。

  对于加边操作,如果c>mid,就区间+1。

  然后这个操作就丢进右边队列。

  如果c<=mid,则对答案无贡献,丢进左边。

  对于询问,就是查询区间和sum。

  如果sum<k,意味着第k大一定不比mid大,丢进左边。

  否则k-=sum,丢进右边(想想主席树的操作)。

  然后就是递归处理了。

  对于上面那个数据结构,树状数组肯定比线段树好是吧......

  我还去%了一波Me2O3学姐的树状数组区间修改......顺便A了codevs的线段树板子。

  HoHo。

  然后就把板子压进结构体我会说

#include    <iostream>
#include    <cstdio>
#include    <cstdlib>
#include    <algorithm>
#include    <vector>
#include    <cstring>
#include    <queue>
#include    <complex>
#include    <stack>
#define LL long long int
#define dob double
using namespace std;

const int N = 100010;
struct Data{
  int type,l,r,c,id;
  bool operator <(const Data &a)const{
    return id<a.id;
  }
}opt[N],que1[N],que2[N];
int n,m,tim;LL Ans[N];

struct BIT{
  LL A1[N],A2[N],A3[N];
  int vis1[N],vis2[N],vis3[N];
  inline int lb(int k){
    return k&-k;
  }
  
  inline void update(LL *A,int *vis,int x,int val){
    for(;x<=n;x+=lb(x)){
      if(vis[x]!=tim)A[x]=0;
      A[x]+=val;vis[x]=tim;
    }
  }
  
  inline LL query(LL *A,int *vis,int x,LL ans=0){
    for(;x;x-=lb(x)){
      if(vis[x]!=tim)A[x]=0;
      ans+=A[x];vis[x]=tim;
    }
    return ans;
  }
  
  inline void add(int l,int r,int dt){
    update(A2,vis2,l,dt);update(A2,vis2,r+1,-dt);
    update(A3,vis3,l,dt*l);update(A3,vis3,r+1,-dt*(r+1));
  }
  
  inline LL sum(int l,int r){
    LL sl=A1[l-1]+l*query(A2,vis2,l-1)-query(A3,vis3,l-1);
    LL sr=A1[r]+(r+1)*query(A2,vis2,r)-query(A3,vis3,r);
    return sr-sl;
  }
}T;

inline int gi(){
  int x=0,res=1;char ch=getchar();
  while(ch>'9'||ch<'0'){if(ch=='-')res*=-1;ch=getchar();}
  while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
  return x*res;
}

/*
  整体二分?
  二分答案c,求:
  solve(optl,optr,ansl,ansr);
  即work出操作在[ol,or],答案在[al,ar]?
  先搞出一个答案mid,再把opt扫一边。
  
  如果是1 
  如果c>mid,就丢进树状数组里面。
  区间+1?
  三个树状数组的事情。
  然后把操作扔进右边QwQ
  如果c<=mid 就不管它
  然后把操作丢进左边。
  
  如果是2
  查询一下和。
  如果sum<k
  则意味着前面的操作里凑不出k个比mid大的数。
  则第k大一定比mid小。
  丢进左边。
  否则 丢进右边。
  然后
  solve(optl,opt1+sizel-1,ansl,mid);
  solve(optr-size2+1,optr,mid+1,ansr);
  直到l==r为止。
*/

inline void solve(int optl,int optr,int l,int r){
  if(optl>optr)return;++tim;
  if(l==r){
    for(int i=optl;i<=optr;++i)
      if(opt[i].type==2)
        Ans[opt[i].id]=l;
    return;
  }
  int mid=((l+r+2*n)>>1)-n,tot1=0,tot2=0;
  for(int i=optl;i<=optr;++i){
    if(opt[i].type==1){
      if(opt[i].c>mid){
        T.add(opt[i].l,opt[i].r,1);
        que2[++tot2]=opt[i];
      }
      else que1[++tot1]=opt[i];
    }
    else{
      LL sum=T.sum(opt[i].l,opt[i].r);
      if(sum<opt[i].c){
        opt[i].c-=sum;
        que1[++tot1]=opt[i];
      }
      else que2[++tot2]=opt[i];
    }
  }
  int k=optl;
  for(int i=1;i<=tot1;++i)opt[k++]=que1[i];
  for(int i=1;i<=tot2;++i)opt[k++]=que2[i];
  solve(optl,optl+tot1-1,l,mid);
  solve(optr-tot2+1,optr,mid+1,r);
}

int main()
{
  freopen("K大数查询.in","r",stdin);
  freopen("K大数查询.out","w",stdout);
  n=gi();m=gi();
  for(int i=1;i<=m;++i){
    opt[i].type=gi();
    opt[i].l=gi();opt[i].r=gi();
    opt[i].c=gi();opt[i].id=i;
  }
  solve(1,m,-n,n);sort(opt+1,opt+m+1);
  for(int i=1;i<=m;++i)
    if(opt[i].type==2)
      printf("%lld
",Ans[i]);
  fclose(stdin);fclose(stdout);
  return 0;
}
K大数查询
原文地址:https://www.cnblogs.com/fenghaoran/p/7429950.html