BZOJ 3110 [Zjoi2013]K大数查询(整体二分+树状数组)

来源:互联网 发布:淘宝快递投诉怎么撤销 编辑:程序博客网 时间:2024/05/16 14:00

3110: [Zjoi2013]K大数查询

Time Limit: 20 Sec  Memory Limit: 512 MB
Submit: 9615  Solved: 2842
[Submit][Status][Discuss]

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,N,M<=50000

a<=b<=N

1操作中abs(c)<=N

2操作中c<=Maxlongint



        其实呢,这题和之前写过的那个动态区间第K大很类似,只不过那个是修改,而这个是插入,但是同样都可以用线段树套Treap来实现。但是今天要说的是另外一种更加巧妙的分治方法,使得编程复杂度大幅下降——整体二分。

        首先,我们知道,如果仅仅只是询问区间第K大,我们用的是二分答案。具体做法是二分一个数字,然后看区间内有多少个数字比他大,如果恰好有K-1个,那么枚举到的这个数字就是区间第K大。对于这道题目,我们同样也可以运用这种思想,只不过我们还要把这个所有的操作也和这个二分答案的过程一起做,所以顾名思义这个方法叫做整体二分。我定义solve(1,m,1,n)表示解决区间[1,m]的操作,然后他们的答案在区间[1,n]中。

        类似与二分答案,我要动态的统计大于mid的数字的出现次数。于是从第一个操作开始,如果是插入操作,而且插入的数字大于mid,那么把执行此操作并把这个操作丢入操作队列B中,否则不执行,操作丢入操作队列A中;如果是查询操作,那么我们就查询对应区间大于mid的数字数量,如果大于K,那么说明mid偏小了,再把它丢入操作队列B中,如果小于说明mid偏大了,K减去大于mid的数量之后再把这个操作丢入操作队列A中。执行完一遍之后,我们再来看看操作队列A和操作队列B中分别是什么。操作队列A中含有的是插入数字小于等于mid的插入操作,和假设以mid为第K大但是发现比他大的数字小于K-1的查询操作;操作队列B中是插入数字大于mid的插入操作,和假设以mid为第K大但是发现比他大的数字大于K-1的查询操作。可以看出,经过这样一个整体二分的过程,我们把所有操作分成了两个部分,而且两个部分内又是各自有着时间的顺序。我们假设操作序列A的长度为lenA,那么我们接下来要做的就是solve(1,lenA,1,mid)和solve(lenA+1,m,mid+1,n)。

        精髓在于,先分再治。把操作和答案一起二分,然后二分的同时把操作和答案都分成两个部分,然后在分别治。然后在处理的过程中,我们需要一个支持区间修改,区间查询的数据结构,这里我用了树状数组,顺便补充一下模板,毕竟这个非常的短小精悍。然后又涉及到一个清零的问题,因为每次治完之后树状数组需要进行清零,这里有两种方式,一是打一个标签,实际上是时间戳,如果时间戳不同,那么在查询和修改的时候首先单个清零;另一种是在治完之后,重现便利操作,把之前更新的减去。为了方便我选择了后者,但是在常数上可能会更慢。值得注意的是,树状数组要使用LL,毕竟加的数字可能比较多。具体见代码:

#include<bits/stdc++.h>#define lowbit(x) x&-x#define LL long long#define N 100010using namespace std;struct node{    int op,l,r;    LL k;int id;} p[N],tmp[2][N];int n,m,ans[N];struct EX_BIT//支持区间修改、区间查询的树状数组{    struct binaryIndexTree    {        LL c[N];        void init(){memset(c,0,sizeof(c));}        void update(int x,LL k){for(;x<=n;c[x]+=k,x+=lowbit(x));}        LL sum(int x){LL ans=0;for(;x>0;ans+=c[x],x-=lowbit(x));return ans;}    } BIT1,BIT2;    void init(){BIT1.init();BIT2.init();}    LL getsum(int l,int r){return sum(r)-sum(l-1);}    void update(int l,int r,LL x){add(l,x);add(r+1,-x);}    LL sum(LL x){return (x+1)*BIT1.sum(x)-BIT2.sum(x);}    void add(int x,LL k){BIT1.update(x,k);BIT2.update(x,x*k);}} BIT;void solve(int L,int R,int l,int r)//整体二分{    if (L>R) return;    if (l==r)//如果到了单位答案区间,那么答案确定了    {        for(int i=L;i<=R;i++)            if (p[i].op==2) ans[p[i].id]=l;        return;    }    int t1=0,t2=0,mid=(l+r)>>1;    for(int i=L;i<=R;i++)    {        if (p[i].op==1)        {            if (p[i].k<=mid) tmp[0][t1++]=p[i];//对于插入操作,分为大于mid和小于等于mid两种情况            else tmp[1][t2++]=p[i],BIT.update(p[i].l,p[i].r,1);        } else        {            LL temp=BIT.getsum(p[i].l,p[i].r);            if (temp<p[i].k) p[i].k-=temp,tmp[0][t1++]=p[i];//查询操作则类似与普通二分答案,看数量是否大于K                                     else tmp[1][t2++]=p[i];        }    }    int Mid=L+t1;    for(int i=L;i<Mid;i++)//临时数组数据转移    {        p[i]=tmp[0][i-L];        if (p[i].op==1&&p[i].k>mid) BIT.update(p[i].l,p[i].r,-1);    }    for(int i=Mid;i<=R;i++)    {        p[i]=tmp[1][i-Mid];        if (p[i].op==1&&p[i].k>mid) BIT.update(p[i].l,p[i].r,-1);    }    solve(L,Mid-1,l,mid); solve(Mid,R,mid+1,r);//分开成两部分去治}int main(){    scanf("%d%d",&n,&m);    for(int i=1;i<=m;i++)    {        int op,l,r,k;        scanf("%d%d%d%d",&op,&l,&r,&k);        p[i]=node{op,l,r,k,i};    }    solve(1,m,1,n);    for(int i=1;i<=m;i++)        if (ans[i]!=0) printf("%d\n",ans[i]);    return 0;}

阅读全文
0 0
原创粉丝点击