您的位置:首页 > 其它

BZOJ3110 ZJOI2013 K大数查询

2017-08-25 22:35 357 查看

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
,que1
,que2
;
int n,m,tim;LL Ans
;

struct BIT{
LL A1
,A2
,A3
;
int vis1
,vis2
,vis3
;
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\n",Ans[i]);
fclose(stdin);fclose(stdout);
return 0;
}


K大数查询
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: