此题大概就是维护一个序列,支持两个操作:
- 区间加
- 区间取中位数(在此题中已经确定取的是所有数的中位数)
显然此题要求在线,所以莫队萎了。所以为什么要用莫队呢?
咳咳,不说那么多,进入正题。
1.区间加操作
当然可以打个暴力。
int l,r;
scanf("%d%d",&l,&r); //表示将区间 [l,r] 内的所有数加上1。
for(int i=l;i<=r;i++){
a[i]++; //a[] 是原数组
}
不过单次修改复杂度已经达到了恐怖的 , 次修改就成了 。
再看看 的数据范围,复杂度立马爆炸。
好吧,考虑优化。
各位大佬可以写一个线段树/树状数组/分块 等搞♂基高级数据结构,不过此题最简单的做法,还是差分。
此时才算真正进入正题
差分基本思想:对于原数组 ,我们定义一个新数组 表示数组 的差分数组,且有 。
那么,如果我们将 加上 ,根据差分数组的定义,我们有 。
而此时 加上了 ,所以,相应地, 也加上了 。
同样是根据差分数组的定义,,而此时 加上了 ,所以,相应地, 也加上了 。
依此类推,只要将 加上 ,我们就能将 全部加上 。(此处 指数组大小)
不过,题目中要求的是将区间 加上一个值诶,这一下子往后多加了很多个数,怎么办呢🤔🤔🤔?
其实,只要把 减去 ,那么区间 全部加上了 ,区间 全部减去了 ,
其中,区间 先加上了 ,又减去了 ,刚好抵消,因此不会变。
所以,用这种方法,我们就能做到 修改啦\(o)/!
当然,最后查询的时候,还是要 恢复一下原数组。
代码实现:
scanf("%d%d",&n,&p); //n 是数组大小,p 是操作次数
while(p--){
int l,r;
scanf("%d%d",&l,&r);
d[l]++,d[r+1]--; //刚才说过的差分
}
for(int i=1;i<=n;i++){
a[i]=a[i-1]+d[i]; //还原数组
}
2.查询所有数的中位数
当然可以一个sort
排序之后,输出最中间的那个数。
不过这么做复杂度是 ,虽然可以过(?),但是爱学习的我们怎么会止步于此呢?当然要继续优化啦!
一种方法是用桶排,期望复杂度 ,不过这里不展开说明。
其实,此题就是要求在一个数组里面查询第 大的数啦。
而线性找第 大的数是完全可以做到的。
我们使用类似快速排序的「分治」思想,在使用快速排序「划分」后,把数组 划分成 与 。
此时比较左边元素数量(即 )与 的大小关系,然后判断在左边还是右边递归求解。
代码实现:
int ans,k;
void find_kth(int a[],int l,int r){
if(l==r){ //如果找到了,记录答案
ans=a[l];
return;
}
int i=l,j=r,f=a[(l+r)/2];
do{
while(a[i]<f){ //从左边找比哨兵大的值
i++;
}
while(a[j]>f){ //从右边找比哨兵大的值
j--;
}
if(i<=j){ //如果 i<=j
swap(i,j); //就交换
i++,j--;
}
}while(i<=j);
if(k<=j){ //在左区间
find_kth(a,l,j);
}
else if(i<=k){ //在右区间
find_kth(a,i,r);
}
else{ //既不在左区间,也不在右区间
find_kth(a,j+1,i-1);
}
}
当然,STL
中有一个好用的 nth_element
,其参数为:nth_element(数组名,数组名+第k小元素,数组名+元素个数)
。
作用就是把数组中的第 k
个元素换成这个数组中第 k
小的元素。期望复杂度 。
#include<cstdio>
#include<algorithm>
#define MAXN 1000005
using namespace std;
int a[MAXN],d[MAXN];
int n,p;
int main(){
scanf("%d%d",&n,&p); //n 是数组大小,p 是操作次数
while(p--){
int l,r;
scanf("%d%d",&l,&r);
d[l]++,d[r+1]--; //刚才说过的差分
}
for(int i=1;i<=n;i++){
a[i]=a[i-1]+d[i]; //还原数组
}
nth_element(a+1,a+n/2+1,a+n+1); //STL 大法好!
printf("%d\n",a[n/2+1]);
return 0;
}