BZOJ3065 带插入区间K小值
作者:互联网
题意
从前有n只跳蚤排成一行做早操,每只跳蚤都有自己的一个弹跳力a[i]。跳蚤国王看着这些跳蚤国欣欣向荣的情景,感到非常高兴。这时跳蚤国王决定理性愉悦一下,查询区间k小值。他每次向它的随从伏特提出这样的问题: 从左往右第x个到第y个跳蚤中,a[i]第k小的值是多少。
这可难不倒伏特,他在脑袋里使用函数式线段树前缀和的方法水掉了跳蚤国王的询问。
这时伏特发现有些跳蚤跳久了弹跳力会有变化,有的会增大,有的会减少。
这可难不倒伏特,他在脑袋里使用树状数组套线段树的方法水掉了跳蚤国王的询问。(orz 主席树)
这时伏特发现有些迟到的跳蚤会插入到这一行的某个位置上,他感到非常生气,因为……他不会做了。
请你帮一帮伏特吧。
快捷版题意:带插入、修改的区间k小值在线查询。
原序列长度 <= 35000
插入个数 <= 35000,修改个数 <= 70000,查询个数 <= 70000 ,0 <= 每时每刻的权值 <= 70000
分析
参照hzwer的题解。
得支持插入的树套树。由于没法旋转,所以只能选择替罪羊树。
学习了一下替罪羊树,具体参见WJMZBMR《重量平衡树和后缀平衡树在信息学奥赛中的应用》。维基百科上面有证明。
替罪羊树是一种不用旋转的平衡树,若一棵子树的左或右子树大小超过其大小55%-80%则暴力重构这棵子树,以此来维护平衡性,每个结点的期望重构次数是logn,实现可以自己脑补一下
在替罪羊树每个结点放一棵包含该子树所有结点的权值线段树,也就是平衡树套权值线段树
- 由于外层是平衡树,那么就能实现插入一个结点:找到它的位置,在根到其路径上所有结点的线段树中插入这个值
- 查询区间第K大:找到这个区间包含若干棵子树,拿出他们的根的权值线段树,一起做个二分
- 修改与插入类似
- 当外层平衡树失衡的时候重构之。
由于内存不够,我们还需要回收垃圾,即对数组的重复使用
时间复杂度
- 查询区间第K大:我觉得把那些节点提取出来就是一个玄学操作,复杂度?据洛谷管理员noip说是\(O(\log n)\)的,那么查第k大就是\(O(\log^2n)\)的。但是我个人感觉这不是正确的上界。
- 重构:由于要回收节点,所以共用节点很麻烦,能力限制使我不能写线段树合并。但是暴力重构的复杂度还是\(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;
}
标签:BZOJ3065,ch,return,int,sum,tree,插入,小值,sl 来源: https://www.cnblogs.com/autoint/p/10383048.html