题目描述

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

输入

第一行一个正整数n,表示原来有n只跳蚤排成一行做早操。
第二行有n个用空格隔开的非负整数,从左至右代表每只跳蚤的弹跳力。
第三行一个正整数q,表示下面有多少个操作。
下面一共q行,一共三种操作对原序列的操作:(假设此时一共m只跳蚤)
1. Q x y k: 询问从左至右第x只跳蚤到从左至右第y只跳蚤中,弹跳力第k小的跳蚤的弹跳力是多少。 (1 <= x <= y <= m, 1 <= k <= y - x + 1)
2. M x val: 将从左至右第x只跳蚤的弹跳力改为val。 (1 <= x <= m)
3. I x val: 在从左至右第x只跳蚤的前面插入一只弹跳力为val的跳蚤。即插入后从左至右第x只跳蚤是我刚插入的跳蚤。 (1 <= x <= m + 1)

为了体现在线操作,设lastAns为上一次查询的时候程序输出的结果,如果之前没有查询过,则lastAns = 0。
则输入的时候实际是:
Q _x _y _k ------> 表示 Q _x^lastAns _y^lastAns _k^lastAns
M _x _val  ------> 表示 M _x^lastAns _val^lastAns
I _x _val  ------> 表示 I _x^lastAns _val^lastAns
简单来说就是操作中输入的整数都要异或上一次询问的结果进行解码。

(祝Pascal的同学早日转C++,就不提供pascal版的描述了。)

输出

对于每个询问输出回答,每行一个回答。

样例输入

10
10 5 8 28 0 19 2 31 1 22
30
I 6 9
M 1 11
I 8 17
M 1 31
M 6 26
Q 2 7 6
I 23 30
M 31 7
I 22 27
M 26 18
Q 26 17 31
I 5 2
I 18 13
Q 3 3 3
I 27 19
Q 23 23 30
Q 5 13 5
I 3 0
M 15 27
Q 0 28 13
Q 3 29 11
M 2 8
Q 12 5 7
I 30 19
M 11 19
Q 17 8 29
M 29 4
Q 3 0 12
I 7 18
M 29 27

样例输出

28
2
31
0
14
15
14
27
15
14


题解

替罪羊树套权值线段树

替罪羊树:一种不基于旋转或分裂的平衡树,当某子树大小占其父亲子树大小超过一定比例时暴力重构。裸的替罪羊树的时间复杂度为均摊最坏$O(n\log n)$。由于其没有旋转或分裂的操作,因此常常被用于树套树的外层。

那么对于本题,由于有定点插入操作,因此需要使用某种平衡树。同时要查询区间k小值,所以还要使用某维护无序序列的数据结构。

考虑把它们套起来,那么平衡树需要放在外层,因为平衡树放在内层的话无法支持插入的同时查询排名在某区间内的数的个数。

由于平衡树放在了外层,因此不能选择大量旋转或分裂的平衡树。此时可以选择替罪羊树(据说Treap也可以,但非常麻烦)。

那么思路就很清晰了:对于外层替罪羊树的每一个节点,维护自己对应的权值线段树和整棵子树所有权值的权值线段树。插入、修改时在经过的点上修改,查询时把区间对应的每 个/堆 节点的权值线段树拿出来,放到一起跑线段树上二分即可。这两个操作的时间复杂度均为$O(n\log^2n)$。

而对于重构操作则较为复杂:替罪羊树最坏情况下重构的节点个数为$n\log n$,而每个节点要进行$\log n$次时间为$O(\log n)$的权值线段树上插入操作(原谅我不会完整保留两棵树的线段树合并),因此时间复杂度为$O(n\log^3n)$。实际上远小于这个理论极限复杂度。

总的理论极限时间复杂度为$O(n\log^3n)=O(能过)$。

这里有一个小优化:当一次插入使得多个地方需要重构时,只重构最上边的(即最大的子树),这样可以避免不必要的重构操作,降低常数。

另外本题需要垃圾回收否则炸空间。

#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define m 70000
#define N 70010
using namespace std;
struct scg
{
int v , ls , rs , si , wr , tr;
}a[N];
struct seg
{
int ls , rs , si;
}b[20000010];
queue<int> q;
char str[5];
int root , n , pos[N] , tot , qr[100] , qt;
void update(int p , int a , int l , int r , int &x)
{
if(!x) x = q.front() , q.pop();
b[x].si += a;
if(!b[x].si) {x = 0; return;}
if(l == r) return;
int mid = (l + r) >> 1;
if(p <= mid) update(p , a , l , mid , b[x].ls);
else update(p , a , mid + 1 , r , b[x].rs);
}
void del(int &x)
{
if(!x) return;
q.push(x);
del(b[x].ls) , del(b[x].rs) , b[x].si = 0 , x = 0;
}
int query(int k , int l , int r)
{
if(l == r) return l;
int mid = (l + r) >> 1 , i , sum = 0;
for(i = 1 ; i <= qt ; i ++ ) sum += b[b[qr[i]].ls].si;
if(k <= sum)
{
for(i = 1 ; i <= qt ; i ++ ) qr[i] = b[qr[i]].ls;
return query(k , l , mid);
}
else
{
for(i = 1 ; i <= qt ; i ++ ) qr[i] = b[qr[i]].rs;
return query(k - sum , mid + 1 , r);
}
}
int build(int l , int r)
{
if(l > r) return 0;
int mid = (l + r) >> 1 , i;
for(i = l ; i <= r ; i ++ ) update(a[pos[i]].v , 1 , 0 , m , a[pos[mid]].tr);
a[pos[mid]].ls = build(l , mid - 1) , a[pos[mid]].rs = build(mid + 1 , r) , a[pos[mid]].si = r - l + 1;
return pos[mid];
}
void dfs(int &x)
{
if(!x) return;
dfs(a[x].ls) , pos[++tot] = x , dfs(a[x].rs);
a[x].si = 0 , del(a[x].tr) , x = 0;
}
void insert(int p , int &x , int v , bool flag)
{
if(!x)
{
x = ++n , a[x].v = v , a[x].si = 1 , update(v , 1 , 0 , m , a[x].wr) , update(v , 1 , 0 , m , a[x].tr);
return;
}
bool tag;
update(v , 1 , 0 , m , a[x].tr) , a[x].si ++ ;
if(p <= a[a[x].ls].si) tag = ((a[a[x].ls].si + 1) * 4 > (a[x].si + 1) * 3) , insert(p , a[x].ls , v , tag || flag);
else tag = ((a[a[x].rs].si + 1) * 4 > (a[x].si + 1) * 3) , insert(p - a[a[x].ls].si - 1 , a[x].rs , v , tag || flag);
if(!flag && tag) tot = 0 , dfs(x) , x = build(1 , tot);
}
int find(int p , int x)
{
if(p <= a[a[x].ls].si) return find(p , a[x].ls);
else if(p > a[a[x].ls].si + 1) return find(p - a[a[x].ls].si - 1 , a[x].rs);
else return a[x].v;
}
void modify(int p , int x , int v1 , int v2)
{
update(v1 , -1 , 0 , m , a[x].tr) , update(v2 , 1 , 0 , m , a[x].tr);
if(p <= a[a[x].ls].si) modify(p , a[x].ls , v1 , v2);
else if(p > a[a[x].ls].si + 1) modify(p - a[a[x].ls].si - 1 , a[x].rs , v1 , v2);
else del(a[x].wr) , update(v2 , 1 , 0 , m , a[x].wr) , a[x].v = v2;
}
void split(int b , int e , int x)
{
if(b <= 1 && e >= a[x].si)
{
qr[++qt] = a[x].tr;
return;
}
if(b <= a[a[x].ls].si + 1 && e >= a[a[x].ls].si + 1) qr[++qt] = a[x].wr;
if(b <= a[a[x].ls].si) split(b , e , a[x].ls);
if(e > a[a[x].ls].si + 1) split(b - a[a[x].ls].si - 1 , e - a[a[x].ls].si - 1 , a[x].rs);
}
int main()
{
int k , i , x , y , z , last = 0;
for(i = 1 ; i <= 20000000 ; i ++ ) q.push(i);
scanf("%d" , &n);
for(i = 1 ; i <= n ; i ++ ) scanf("%d" , &a[i].v) , update(a[i].v , 1 , 0 , m , a[i].wr) , pos[i] = i;
root = build(1 , n);
scanf("%d" , &k);
while(k -- )
{
scanf("%s%d%d" , str , &x , &y) , x ^= last , y ^= last;
if(str[0] == 'Q') qt = 0 , split(x , y , root) , scanf("%d" , &z) , z ^= last , printf("%d\n" , last = query(z , 0 , m));
else if(str[0] == 'M') modify(x , root , find(x , root) , y);
else insert(x - 1 , root , y , 0);
}
return 0;
}

【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. 【BZOJ3065】带插入区间K小值 替罪羊树+权值线段树

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

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

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

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

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

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

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

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

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

  10. 【学习笔记】浅析平衡树套线段树 & 带插入区间K小值

    常见的树套树 一般来说,在嵌套数据结构中,线段树多被作为外层结构使用. 但线段树毕竟是 静态 的结构,导致了一些不便. 下面是一个难以维护的例子: 带插入区间 \(k\) 小值问题 来源:Luogu ...

随机推荐

  1. 【BZOJ4196】[NOI2015] 软件包管理器(树链剖分)

    点此看题面 大致题意: 有\(n\)个软件包,它们的依赖关系形成一棵树.现在,问你安装或卸载一个软件包,会影响多少个软件包的安装状态. 树链剖分 这道题应该是 树链剖分 算法比较入门的题目吧. 对于安 ...

  2. 2018.6.20 Java考试试题总结(Java语言基础与面向对象编程)最新版

    Java考试试题总结 一.单选题(每题1分 * 50 = 50分) 1.java程序的执行过程中用到一套JDK工具,其中javac.exe指( B ) A.java语言解释器 B.java字节码编译器 ...

  3. Python02 变量

    变量 因为Python是弱变量类型编程语言,所以变量赋值不需要类型声明. 每个变量在内存中创建,都包括变量的标识,名称和数据这些信息. 每个变量在使用前都必须赋值,变量赋值以后该变量才会被创建. 变量 ...

  4. angular4 学习日志(一 依赖注入)

    1.创建一个服务,为了好管理建一个名叫services的文件夹管理所有服务: ng g service services\person 2.在服务中定义一个person 类 : 3.在app.mdul ...

  5. Oracle小知识_长期总结

    更新时间:2018年7月16日 11:22:28 一. 系统 1. 打开防火墙后 Oracle 无法链接 新建1521端口规则. 二.知识 A. 序列 1. nextval ------------- ...

  6. 1043: [HAOI2008]下落的圆盘

    Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 1725  Solved: 743[Submit][Status][Discuss] Descripti ...

  7. PAT 乙级 1078 / 1084

    题目 PAT 乙级 1078 PAT 乙级 1084 题解 1078和1084这两道题放在一块写,主要是因为这两道题的解法和做题思路非常相似:之前我做这一类题没有一个固定的套路,想到哪写到哪,在某种程 ...

  8. forEach 以及 IE兼容

      语法 array.forEach(function(currentValue, index, arr), thisValue) 参数 参数 描述 function(currentValue, in ...

  9. 字符编码笔记:ASCII、Unicode和UTF-8

    1. ASCII码 我们知道,在计算机内部,所有的信息最终都表示为一个二进制的字符串.每一个二进制位(bit)有0和1两种状态,因此八个二进制位就可以组合出256种状态,这被称为一个字节(byte). ...

  10. billard:桌球的走位路线图解

    这些是桌球的一些基础知识,记得刚学会桌球那会儿很强烈的想找到类似图片或资料,好久都找不到,最严重的时候只要一闭上眼睛,满脑子就是桌球的路线,线路图几乎是无处不在,痛苦的是经常能理解过来的很多路线因为杆 ...