不得不说,做过最爽的树套树————

由于有了区间操作,我们很容易把区间看成一棵平衡树,对他进行插入,那么外面一层就是平衡树了,这就与我们之前所见到的不同了。我们之前所见到的大多数是线段树套平衡树而此题中插入时坐标会改变即必须对其找到合适的顺序,而线段树无疑是不支持动态插入的,他维护的是一个静态区间(因为插入一个点整个区间的二分结构可能全部改变,这用他就无法通过区间二分来维护信息了)。所以说我们必须来进行平衡树套线段树,那么平衡树就需要维护区间,而线段树就需要维护权值,之前我们的树套树,在外边一个静态区间线段树里面每个节点都有这个点dfs序所对应区间里的数的有序排列,在这里我们用平衡树来维护区间,那么对于里面的点,YY一下,也是对应于父子关系的一段区间,但我们无法将里面的点有序化,但是我们得到了一个可视且可用的一段取值区间这样我们就可以用动态开点来维护权值信息,一般的平衡树像Splay Treap都需要旋转操作而每次旋转里面的线段树维护的权值信息都会随着父子关系的改变而改变,每次都需要新的线段树(合并也行),常数大到飞起(Treap插入乘个log,Spaly插入乘个log查询log2,虽然看起来没那么差甚至根由更优,但是想一想常数),那么替罪羊就要来当大佬了(表示不会无旋Treap),这样只有每次重构才会将重建(合并)线段树,大约有㏒₂n次重建。

空间复杂度:替罪羊n;线段树如果不修改  ∑(i=1 ~log2n)n*[log2n-∑(j=1~log2n-i+1)[(2i-1)/2i]],平均每个点152个,

如果修改的话2*n+∑(i=1~log₂n-1)[17-∑(j=1~log₂n-j+2)[(2i-1)/2i]]*n*2,平均每个点280个

这样的话我们就需要回收空间了,不仅是在重建的时候,也必须得是在修改的时候(别问我为什么这么说QAQ),把他维持到152个以内

时间复杂度:一开始的点用O(n+nlog₂²n),每次插入O(log₂²n),修改O(log₂²n),查询:每一层被选中的点和根(不要忘了我们是在分平衡树千万不要忘了中间那个点)的个数不超过四个(这个我不会理性的证,我先感性的证一下:1.你把这棵树建出来,开始在同一深度找,找到五个的时候怎么也找不到2.这么吊的数据结构查询怎么也得是log级别的啊),所以就是O(log₂n+log₂3n),总重建O(log₂n*(n+∑(i=1 ~log2n)n*[log2n-∑(j=1~log2n-i+1)[(2i-1)/2i]]+n*log²n))大约10*log²n*n

所以总时间复杂度:O(q*log₂3n)估大一点200000*173也就是109由于每个点10s也就行了

然而人傻自带大常数........

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define MAXN 140100
#define inf 70000
using namespace std;
inline int read()
{
int s=;
char ch=getchar();
while(ch<''||ch>'')ch=getchar();
while(ch>=''&&ch<='')
{
s=(s<<)+(s<<)+ch-;
ch=getchar();
}
return s;
}
const double alpha=0.75;
struct Tree
{
Tree *ch[];
int size,l,r,mid;
}*null,*stack[*MAXN],pool[*MAXN],*now[MAXN];
int Now[MAXN],num,Num;
int list[MAXN],len;
int top;
struct ScapeGoat_Tree
{
ScapeGoat_Tree *ch[];
int size,key;
Tree *root;
bool bad()
{
return size*alpha+<ch[]->size||size*alpha+<ch[]->size;
}
void pushup()
{
size=ch[]->size+ch[]->size+;
}
}*Null,*root,*lst[MAXN],node[MAXN];
int sz;
inline void Init()
{
null=pool;
null->size=null->l=null->r=null->mid=;
null->ch[]=null->ch[]=null;
for(int i=;i<(*MAXN);i++)stack[++top]=pool+i;
Null=node;
Null->size=Null->key=;
Null->ch[]=Null->ch[]=Null;
Null->root=null;
root=Null;
}
inline Tree *New(int l,int r)
{
Tree *p=stack[top--];
p->l=l;
p->r=r;
p->mid=(l+r)>>;
p->size=;
p->ch[]=p->ch[]=null;
return p;
}
inline ScapeGoat_Tree *New(int key)
{
ScapeGoat_Tree *p=&node[++sz];
p->ch[]=p->ch[]=Null;
p->size=;
p->key=key;
p->root=null;
return p;
}
void recovery(Tree *p)
{
if(p==null)return;
recovery(p->ch[]);
stack[++top]=p;
recovery(p->ch[]);
}
void travel(ScapeGoat_Tree *p)
{
if(p==Null)return;
travel(p->ch[]);
lst[++len]=p;
list[len]=p->key;
recovery(p->root);
p->root=null;
travel(p->ch[]);
}
void ins(Tree *&p,int key,int l,int r)
{
if(p==null)p=New(l,r);
p->size++;
if(p->l==p->r)return;
if(p->mid<key) ins(p->ch[],key,p->mid+,r);
else ins(p->ch[],key,l,p->mid);
}
ScapeGoat_Tree *divide(int l,int r)
{
if(l>r)return Null;
int mid=(l+r)>>;
for(int i=l;i<=r;i++)ins(lst[mid]->root,list[i],,inf);
lst[mid]->ch[]=divide(l,mid-);
lst[mid]->ch[]=divide(mid+,r);
lst[mid]->pushup();
return lst[mid];
}
void Ins(ScapeGoat_Tree *p,int key,int pos)
{
ins(p->root,key,,inf);
if(p->ch[]->size+==pos)
{
p->key=key;
return;
}
if(p->ch[]->size>=pos)Ins(p->ch[],key,pos);
else Ins(p->ch[],key,pos-p->ch[]->size-);
}
inline void rebuild(ScapeGoat_Tree *&p)
{
len=;
travel(p);
p=divide(,len);
}
ScapeGoat_Tree **insert(ScapeGoat_Tree *&p,int key,int pos)
{
if(p==Null)
{
p=New(key);
ins(p->root,key,,inf);
return &Null;
}
p->size++;
ins(p->root,key,,inf);
ScapeGoat_Tree **ret;
if(p->ch[]->size+>=pos)ret=insert(p->ch[],key,pos);
else ret=insert(p->ch[],key,pos-p->ch[]->size-);
if(p->bad())ret=&p;
return ret;
}
inline void Insert(int key,int pos)
{
ScapeGoat_Tree **p=insert(root,key,pos);
if(*p!=Null)rebuild(*p);
}
inline int find(int pos)
{
ScapeGoat_Tree *p=root;
while()
if(p->ch[]->size+==pos)return p->key;
else if(p->ch[]->size>=pos)p=p->ch[];
else pos-=p->ch[]->size+,p=p->ch[];
}
void del(Tree *&p,int key)
{
p->size--;
if(p->l==p->r)
{
if(p->size==)
{
stack[++top]=p;
p=null;
}
return;
}
del(p->ch[p->mid<key],key);
if(p->size==)
{
stack[++top]=p;
p=null;
}
}
void Del(ScapeGoat_Tree *p,int pos,int key)
{
del(p->root,key);
if(p->ch[]->size+==pos)return;
if(p->ch[]->size>=pos)Del(p->ch[],pos,key);
else Del(p->ch[],pos-p->ch[]->size-,key);
}
int rank(Tree *p,int key)
{
if(p==null)return ;
if(p->l==p->r)return ;
if(key<=p->mid)return rank(p->ch[],key);
else return p->ch[]->size+rank(p->ch[],key);
}
void pre(ScapeGoat_Tree *p,int l,int r)
{
if(p==Null)return;
if(l<=&&p->size<=r)
{
now[++num]=p->root;
return;
}
if(l<=p->ch[]->size)pre(p->ch[],l,r);
if(l<=p->ch[]->size+&&r>=p->ch[]->size+)Now[++Num]=p->key;
if(p->ch[]->size+<r)pre(p->ch[],l-p->ch[]->size-,r-p->ch[]->size-);
}
inline int Rank(int x)
{
int ans=;
for(int i=;i<=num;i++)
ans+=rank(now[i],x);
for(int i=;i<=Num;i++)
if(x>Now[i])ans++;
else break;
return ans;
}
int lastans=;
inline void work1()
{
int x=read()^lastans,y=read()^lastans,k=read()^lastans;
if(x>y)x^=y^=x^=y;
num=Num=;
pre(root,x,y);
sort(Now+,Now+Num+);
int l=,r=inf,ans=;
while(l<=r)
{
int mid=(l+r)>>;
int tmp=Rank(mid)+;
if(tmp<=k)
ans=mid,l=mid+;
else
r=mid-;
}
lastans=ans;
printf("%d\n",ans);
}
inline void work2()
{
int x=read()^lastans,val=read()^lastans;
Del(root,x,find(x));
Ins(root,val,x);
}
inline void work3()
{
int x=read()^lastans,val=read()^lastans;
Insert(val,x);
}
int main()
{
Init();
int n=read();
for(int i=;i<=n;i++)
{
list[++len]=read();
lst[len]=New(list[len]);
}
root=divide(,len);
int Q=read();
while(Q--)
{
char s[];
scanf("%s",s);
if(s[]=='Q')work1();
if(s[]=='M')work2();
if(s[]=='I')work3();
}
return ;
}

【bzoj3065】: 带插入区间K小值 详解——替罪羊套函数式线段树的更多相关文章

  1. [BZOJ3065]带插入区间K小值 解题报告 替罪羊树+值域线段树

    刚了一天的题终于切掉了,数据结构题的代码真**难调,这是我做过的第一道树套树题,做完后感觉对树套树都有阴影了......下面写一下做题记录. Portal Gun:[BZOJ3065]带插入区间k小值 ...

  2. BZOJ3065 带插入区间K小值 || 洛谷P4278

    这是一道让我崩溃的题...... 然鹅洛谷上时限被改然后只有20分......好像所有人都被卡了(雾) 由于替罪羊树不是依靠旋转操作而是依靠暴力重构的方式维护树的平衡,所以我们可以考虑使用替罪羊树套区 ...

  3. 【函数式权值分块】【块状链表】bzoj3065 带插入区间K小值

    显然是块状链表的经典题.但是经典做法的复杂度是O(n*sqrt(n)*log^2(n))的,出题人明确说了会卡掉. 于是我们考虑每个块内记录前n个块的权值分块. 查询的时候差分什么的,复杂度就是O(n ...

  4. bzoj3065: 带插入区间K小值

    无聊来写了下 一开始发现树高是O(n)的,然后就MLE了,进去看了下发现没有重构! 看了半天发现调用错了函数 然后进去又发现不满足sz = ch[0]->sz + ch[1]->sz + ...

  5. 3065: 带插入区间K小值_树套树_替罪羊树_权值线段树

    经过周六一天,周一3个小时的晚自习,周二2个小时的疯狂debug,终于凭借自己切掉了这道树套树题. Code: #include <cstdio> #include <algorit ...

  6. 【BZOJ3065】带插入区间K小值 替罪羊树+权值线段树

    [BZOJ3065]带插入区间K小值 Description 从前有n只跳蚤排成一行做早操,每只跳蚤都有自己的一个弹跳力a[i].跳蚤国王看着这些跳蚤国欣欣向荣的情景,感到非常高兴.这时跳蚤国王决定理 ...

  7. bzoj 3065: 带插入区间K小值 替罪羊树 && AC300

    3065: 带插入区间K小值 Time Limit: 60 Sec  Memory Limit: 512 MBSubmit: 1062  Solved: 253[Submit][Status] Des ...

  8. 【题解】BZOJ 3065: 带插入区间K小值——替罪羊树套线段树

    题目传送门 题解 orz vfk的题解 3065: 带插入区间K小值 系列题解 一 二 三 四 惨 一开始用了一种空间常数很大的方法,每次重构的时候merge两颗线段树,然后无限RE(其实是MLE). ...

  9. BZOJ 3065 带插入区间K小值(sag套线段树)

    3065: 带插入区间K小值 Time Limit: 60 Sec  Memory Limit: 512 MBSubmit: 4696  Solved: 1527[Submit][Status][Di ...

随机推荐

  1. 【洛谷4005】小Y和地铁(搜索)

    [洛谷4005]小Y和地铁(搜索) 题面 洛谷 有点长. 题解 首先对于需要被链接的两个点,样例中间基本上把所有的情况都给出来了. 但是还缺了一种从下面绕道左边在从整个上面跨过去在从右边绕到下面来的情 ...

  2. 面向对象高级编程(1)-使用__slots__

    使用__slots__ 正常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性.先定义class: >>> ...

  3. java多线程 -- 原子量 变量 CAS

    多线程原子性问题的产生和解决 原子变量:在 java.util.concurrent.atomic 包下提供了一些原子变量. 1. volatile 保证内存可见性,可以查看atomic中变量是使用v ...

  4. NOIWC前的交流题目汇总

    RT 2018.12.27 i207M:BZOJ 4695 最假女选手 以维护最大值为例,记录最大值和严格次大值和最大值的出现次数,然后取min的时候递归到小于最大值但大于次大值修改,这个就是最重要的 ...

  5. Miller-Robin与二次探测

    素数在数论中经常被用到.也是数论的基础之一. 人们一直在讨论的问题是,怎样快速找到素数?或者判断一个数是素数? 1.根号n枚举 原始暴力方法. 2.埃氏筛 每个合数会被筛质因子次数次.复杂度O(Nlo ...

  6. python map对象

    工作中遇到需要将List对象中的元素(list类型)转化为集合(set)类型,转化完成之后需要需要访问其中的元素. 第一步,使用map方法进行转换 data = [[1, 3, 4], [2, 3, ...

  7. ElasticStack系列之六 & 版本冲突处理之乐观锁

    悲观并发控制(PCC) 这一点在关系数据库中被广泛使用.假设这种情况很容易发生,我们就可以阻止对这一资源的访问.典型的例子就是当我们在读取一个数据前先锁定这一行,然后确保只有读取到数据的这个线程可以修 ...

  8. openresty/1.11.2.1性能测试

    测试数据 ab -n -c -k http://127.0.0.1/get_cache_value nginx.conf lua_shared_dict cache_ngx 128m; server ...

  9. vscode nodejs智能提示功能

    1.依赖一些第三方的插件,先安装typings这个包,如果使用的是淘宝镜像,输入cnpm.cmd执行: cnpm i typings -g //cnpm install typings -global ...

  10. Javascript实现返回上一页面并刷新

    今天写了一个小小的提示成功的页面,同时要求返回上一页面,并实现对上一页面的操作进行刷新(例如删除的,添加的),在网上搜寻了一遍,基本上90%的都是说的是用window.history.go(-1), ...