3110: [Zjoi2013]K大数查询
Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 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
1 1 2 1
1 1 2 2
2 1 1 2
2 1 1 1
2 1 2 3
Sample Output
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 100010
using 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;
}