BZOJ3065 带插入区间K小值

带插入区间K小值

从前有n只跳蚤排成一行做早操,每只跳蚤都有自己的一个弹跳力a[i]。跳蚤国王看着这些跳蚤国欣欣向荣的情景,感到非常高兴。这时跳蚤国王决定理性愉悦一下,查询区间k小值。他每次向它的随从伏特提出这样的问题: 从左往右第x个到第y个跳蚤中,a[i]第k小的值是多少。
这可难不倒伏特,他在脑袋里使用函数式线段树前缀和的方法水掉了跳蚤国王的询问。
这时伏特发现有些跳蚤跳久了弹跳力会有变化,有的会增大,有的会减少。
这可难不倒伏特,他在脑袋里使用树状数组套线段树的方法水掉了跳蚤国王的询问。(orz 主席树)
这时伏特发现有些迟到的跳蚤会插入到这一行的某个位置上,他感到非常生气,因为……他不会做了。
请你帮一帮伏特吧。
快捷版题意:带插入、修改的区间k小值在线查询。

原序列长度 <= 35000

插入个数 <= 35000,修改个数 <= 70000,查询个数 <= 70000 ,0 <= 每时每刻的权值 <= 70000

hzwer的题解

得支持插入的树套树。由于没法旋转,所以只能选择替罪羊树。

学习了一下替罪羊树,具体参见WJMZBMR《重量平衡树和后缀平衡树在信息学奥赛中的应用》。维基百科上面有证明。

替罪羊树是一种不用旋转的平衡树,若一棵子树的左或右子树大小超过其大小55%-80%则暴力重构这棵子树,以此来维护平衡性,每个结点的期望重构次数是logn,实现可以自己脑补一下

在替罪羊树每个结点放一棵包含该子树所有结点的权值线段树,也就是平衡树套权值线段树

  1. 由于外层是平衡树,那么就能实现插入一个结点:找到它的位置,在根到其路径上所有结点的线段树中插入这个值
  2. 查询区间第K大:找到这个区间包含若干棵子树,拿出他们的根的权值线段树,一起做个二分
  3. 修改与插入类似
  4. 当外层平衡树失衡的时候重构之。

由于内存不够,我们还需要回收垃圾,即对数组的重复使用

时间复杂度

  1. 查询区间第K大:我觉得把那些节点提取出来就是一个玄学操作,复杂度?据洛谷管理员noip说是\(O(\log n)\)的,那么查第k大就是\(O(\log^2n)\)的。但是我个人感觉这不是正确的上界。
  2. 重构:由于要回收节点,所以共用节点很麻烦,能力限制使我不能写线段树合并。但是暴力重构的复杂度还是\(O(\log^2 n)\)的。

总时间复杂度\(O((n+m) \log^2 n)\),但是常数问题导致我过不了洛谷上面时限1s的题。

#include<bits/stdc++.h>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
	rg T data=0,w=1;
	rg char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-') w=-1;
		ch=getchar();
	}
	while(isdigit(ch))
		data=data*10+ch-'0',ch=getchar();
	return data*w;
}
template<class T>il T read(rg T&x){
	return x=read<T>();
}
typedef long long ll;
using std::vector;

co int N=7e4+1,LG=1e7; // memory limit
int n,m;
// Tree Tao Tree
namespace T{
	// Interval Tree
	int tot,bin[LG];
	int L[LG],R[LG],sum[LG];
	il int construct() {return bin[0]?bin[bin[0]--]:++tot;}
	il void destruct(int&x){
		if(!x) return;
		destruct(L[x]),destruct(R[x]);
		bin[++bin[0]]=x,sum[x]=0,x=0; // edit 2: x=0
	}
	void insert(int&x,int l,int r,int p,int d){
		if(!x) x=construct();
		sum[x]+=d;
		if(!sum[x]){
			destruct(x);
			return;
		}
		if(l==r) return;
		int m=(l+r)/2;
		if(p<=m) insert(L[x],l,m,p,d);
		else insert(R[x],m+1,r,p,d);
	}
	int query(vector<int>&x,co vector<int>&vs,int l,int r,int k){
		if(l==r) return l;
		int s=0,m=(l+r)/2;
		for(rg unsigned i=0;i<x.size();++i)
			s+=sum[L[x[i]]];
		for(rg unsigned i=0;i<vs.size();++i)
			s+=(l<=vs[i]&&vs[i]<=m);
		if(s>=k){
			for(rg unsigned i=0;i<x.size();++i)
				x[i]=L[x[i]];
			return query(x,vs,l,m,k);
		}
		else{
			for(rg unsigned i=0;i<x.size();++i)
				x[i]=R[x[i]];
			return query(x,vs,m+1,r,k-s);
		}
	}
	// Scapegoat Tree
	co double ratio=0.75; // edit 3:double
	int root,pot[N];
	int val[N],tree[N],ch[N][2];
	void build(int&t,int l,int r){
		if(l>r) return;
		int m=(l+r)/2;
		t=pot[m];
		for(rg int i=l;i<=r;++i)
			insert(tree[t],0,7e4,val[pot[i]],1);
		if(l==r) return;
		build(ch[t][0],l,m-1),build(ch[t][1],m+1,r); // edit 1:m-1 for balanced tree
	}
	void split(int t,int l,int r,vector<int>&x,vector<int>&vs){
		int sl=sum[tree[ch[t][0]]],st=sum[tree[t]];
		if(l==1&&r==st){
			x.push_back(tree[t]);
			return;
		}
		if(l<=sl+1&&sl+1<=r) vs.push_back(val[t]);
		if(r<=sl) split(ch[t][0],l,r,x,vs);
		else if(l>sl+1) split(ch[t][1],l-sl-1,r-sl-1,x,vs); // edit 3: 1,not 0
		else{
			if(l<=sl) split(ch[t][0],l,sl,x,vs);
			if(r>sl+1) split(ch[t][1],1,r-sl-1,x,vs);
		}
	}
	int query(int t,int l,int r,int k){
		vector<int> x,vs;
		split(t,l,r,x,vs);
		return query(x,vs,0,7e4,k);
	}
	int modify(int t,int k,int v){
		insert(tree[t],0,7e4,v,1);
		int o,sl=sum[tree[ch[t][0]]];
		if(sl+1==k) o=val[t],val[t]=v;
		else if(sl>=k) o=modify(ch[t][0],k,v);
		else o=modify(ch[t][1],k-sl-1,v);
		insert(tree[t],0,7e4,o,-1);
		return o;
	}
	void remove(int&t){
		if(!t) return;
		remove(ch[t][0]);
		pot[++pot[0]]=t;
		remove(ch[t][1]);
		destruct(tree[t]),t=0; // edit 2:t=0
	}
	il void rebuild(int&t){
		remove(t);
		build(t,1,pot[0]);
		pot[0]=0;
	}
	int tmp;
	void insert(int&t,int k,int v){
		if(!t){
			t=++n;
			val[t]=v,insert(tree[t],0,7e4,v,1);
			return;
		}
		insert(tree[t],0,7e4,v,1);
		int sl=sum[tree[ch[t][0]]];
		if(sl>=k) insert(ch[t][0],k,v);
		else insert(ch[t][1],k-sl-1,v);
		if(sum[tree[t]]*ratio>std::max(sum[tree[ch[t][0]]],sum[tree[ch[t][1]]])){
			if(tmp){ // edit 4: when rebuilding must change ch as well
				rebuild(ch[t][tmp==ch[t][1]]);
				tmp=0;
			}
		}
		else tmp=t;
	}
}

int main(){
//	freopen("BZOJ3065.in","r",stdin);
//	freopen("BZOJ3065.out","w",stdout);
	read(n);
	for(rg int i=1;i<=n;++i)
		read(T::val[i]),T::pot[i]=i;
	T::build(T::root,1,n);
	read(m);
	int lastans=0;
	while(m--){
		char opt[2];
		scanf("%s",opt);
		switch(opt[0]){
			case 'Q':{
				int l=read<int>()^lastans,r=read<int>()^lastans,k=read<int>()^lastans;
				printf("%d\n",lastans=T::query(T::root,l,r,k));
				break;
			}
			case 'M':{
				int k=read<int>()^lastans,v=read<int>()^lastans;
				T::modify(T::root,k,v);
				break;
			}
			case 'I':{
				int k=read<int>()^lastans,v=read<int>()^lastans;
				T::insert(T::root,k-1,v);
				if(T::tmp){
					T::rebuild(T::root);
					T::tmp=0;
				}
				break;
			}
			default:assert(0);
		}
	}
	return 0;
}

posted on 2019-02-15 12:41  autoint  阅读(215)  评论(0编辑  收藏  举报

导航