20200405 T2 mid 随机序列插入求中位数

题目描述:

有一个多重集,初始为空。n次操作,每次往多重集插入一个整数,求每次插入完后多重集的中位数,输出所有中位数的异或和。
给出第一次操作的插入的数和 n n n,其余的数按某种方式随机生成。
50 % 50\% 50%的数据, n ≤ 1 0 6 n\le10^6 n106
100 % 100\% 100%的数据, n ≤ 3 ∗ 1 0 7 n\le3*10^7 n3107
Upd:LOJ上有:#6137. 「2017 山东三轮集训 Day4」Mid

题目分析:

最慢但是最无脑的做法:平衡树(splay)。

有点技巧但是卡不过100%的做法:最大堆和最小堆维护序列中位数
具体就是记录当前的中位数 m i d mid mid,和小于等于它的元素组成的最大堆,大于等于它的元素组成的最小堆,插入时跟mid比大小,然后根据两边堆的大小调整mid。需要一次插入和一次删除。

堆的这种做法也可以换成set,这样就可以直接在一个set里面迭代器移动。基本每次需要一次插入和一次移动。
因为序列是随机的,所以可以设一个阈值 l i m lim lim,当set的元素个数大于 l i m lim lim时每插入一个数就在对应的那一边去掉头或尾(当然这种做法如果在某一边插很多就会错),这样set的log就被降下来了(虽然会多一个删除操作),本地实测当 l i m = 1 e 4 lim=1e4 lim=1e4时可以在 1.67 s 1.67s 1.67s内通过。

标程是在第一种堆的做法上利用随机继续优化,把中间的一个数换成一个数组(双端队列),同样设一个阈值 l i m lim lim,如果插入的数小于左边最大堆的堆顶就往左边插,大于右边最小堆的堆顶就往右边插,否则就往中间暴力插入(移动数组)。
插入完后如果中位数落在左边堆里,就将左边的堆弹出到数组的头部。
如果中位数落在右边堆里,就将右边的堆弹出到数组的尾部。
然后检查数组的大小是否超过 l i m lim lim,如果超过就看哪边堆小就往哪边放。
然后中位数就必然落在数组中了,直接返回即可。
当n较大后插入基本上会落在两边的堆中,由于序列随机,对比第一种做法,中间的数组相当于起到了缓冲的作用,所以调整次数相对减少。
在这里插入图片描述
平衡 s 2 + n / s s^2+n/s s2+n/s,取 s = 246 s=246 s=246理论复杂度最优,实际上取几百都差不多。本地测 1.6 s 1.6s 1.6s

Code(手写插入比push_heap快,手写删除却比pop_heap慢…):

#include<bits/stdc++.h>
using namespace std;
const int N = 15000005, S = 500, P = 1e9+7;
struct Heap{
	int a[N],n; Heap(){a[0]=2e9;}
	inline void push(int x){
		int i=++n;
		for(;x>a[i>>1];i>>=1) a[i]=a[i>>1];
		a[i]=x;
	}
	inline void pop(){pop_heap(a+1,a+1+n),n--;}
	inline int top(){return a[1];}
}L,R;
int q[N/S*2],l=N/S+1,r=N/S,cnt;
inline void force(int x){
	int i=r;
	for(;i>=l&&x<q[i];i--) q[i+1]=q[i];
	q[i+1]=x,r++;
}
int ins(int x){
	int k=(++cnt+1)>>1;
	if(r-l<S) force(x);
	else if(x<=q[l]) L.push(x);
	else if(x>=q[r]) R.push(-x);
	else {if(L.n<R.n) L.push(q[l++]); else R.push(-q[r--]); force(x);}
	if(L.n>=k) q[--l]=L.top(),L.pop(),R.push(-q[r--]);
	if(R.n>cnt-k) q[++r]=-R.top(),R.pop(),L.push(q[l++]);
	return q[l+k-L.n-1];
}
int main()
{
	freopen("mid.in","r",stdin);
	freopen("mid.out","w",stdout);
	int n,x,ans=0,last;
	scanf("%d%d",&n,&x);
	for(int i=1;i<=n;i++)
		ans^=last=ins(x),x=(1714636915ll*x+1681692777)%P*(846930886ll*last%P+1804289383)%P;
	printf("%d\n",ans);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值