【洛谷2617_BZOJ1901】Dynamic Rankings(树套树)
题目:
BZOJ 1901 是权限题,\(n=10^4\) ,内存 128 MB ;洛谷 2617 \(n=10^5\) ,内存 1024 MB ,数据比较坑。
分析:
蒟蒻初学树套树……
先谈谈个人对树套树的理解。树形数据结构每个结点上维护的信息可以分为两种:一种是根据题目需要维护的数据(我喜欢称之为 “数据字段”——这个名字可能是我自己起的),另一种是为了形成数据 “结构” 而维护的数据(我称为 “结构字段” ——也是我自己起的名字)。比如一棵维护区间和的平衡树,它的结点的 “数据字段” 就是该结点对应区间的区间和,而 “结构字段” 就是指向左、右儿子和父亲的指针。对于同一种数据结构中的结点,“结构字段” 通常是固定的,比如 Splay 一定是两个儿子和父亲的指针,动态开点线段树一定是两个儿子;而 “数据字段” 则根据题目需要决定,如维护最值、区间和等。通常 “在结点上维护 XX ” 指的是 “结点的数据字段是(或 ‘表示’) XX ” 。
树套树并不是一种独立的数据结构。以往见到的数据结构(比如线段树、平衡树)的 “数据字段” 通常是整数,而在树套树中,“数据字段” 是另一个数据结构。以 STL 中的 set 为例,下面这条语句定义了一个 “数据字段” 是整数的 set :
set<int> s;
而下面这句定义一个 “数据字段” 是一个维护整数的 set 的 set ,可以称为 “红黑树套红黑树” :
set< set<int> > s;
口胡结束,开始看这道题。如果询问的不是第 \(k\) 大而是最大,那么开一棵维护区间最大值线段树,把询问拆成 \(\log n\) 个线段树上的结点,把这些结点上维护的最大值(数据字段)取最大值就是答案;如果询问的不是区间而是全局,那么开一棵权值线段树维护所有数的权值,在上面二分找第 \(k\) 大即可。把以上两种思想结合起来,可以考虑建立一棵区间线段树,每个结点维护一棵权值线段树,在上面维护这个区间中数的权值。
询问的时候,先找出对应的 \(\log n\) 个区间,然后记录这 \(\log n\) 棵内层权值线段树的根。开始在内层线段树上走,每次判断 \(\log n\) 个左儿子的和是否不小于 \(k\) ,如果是则把这 \(\log n\) 个根都移动到各自的左儿子,否则移动到右儿子。
修改的时候把外层树从根到叶子一条链上所有内层树都改一遍即可。
上述两种操作的时间复杂度都是 \(O(\log n\log M)\) ,其 中\(M\) 是最大权值(离散化后就是 \(n\) )。
内层线段树必须动态开点,这样外层树每个结点对应的内层树的总结点树是 \(a\log M\),其中 \(a\) 是这个外层结点对应的区间长度。因为所有外层树所有结点的长度之和是 \(n\log n\) ,所以总空间复杂度是 \(n\log^2 n\) 。如果不动态开点每个外层结点都对应 \(O(M)\) 结点的内层树,空间瞬间爆炸。
代码:
这个方法比较菜,不离散化洛谷会 T 一半,离散化 + O2 在洛谷上才能过。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cctype>
using namespace std;
namespace zyt
{
template<typename T>
inline bool read(T &x)
{
char c;
bool f = false;
x = 0;
do
c = getchar();
while (c != EOF && c != '-' && !isdigit(c));
if (c == EOF)
return false;
if (c == '-')
f = true, c = getchar();
do
x = x * 10 + c - '0', c = getchar();
while (isdigit(c));
if (f)
x = -x;
return true;
}
inline bool read(char &c)
{
do
c = getchar();
while (c != EOF && !isgraph(c));
return c != EOF;
}
template<typename T>
inline void write(T x)
{
static char buf[20];
char *pos = buf;
if (x < 0)
putchar('-'), x = -x;
do
*pos++ = x % 10 + '0';
while (x /= 10);
while (pos > buf)
putchar(*--pos);
}
const int N = 1e4 + 10, B = 16;
int n, m, maxx, arr[N];
namespace Inner_Segment_Tree
{
struct node
{
int num, lt, rt;
}tree[N * B * B];
int cnt;
int add(int rot, const int lt, const int rt, const int pos, const int x)
{
if (!rot)
rot = ++cnt;
tree[rot].num += x;
if (lt != rt)
{
int mid = (lt + rt) >> 1;
if (pos <= mid)
tree[rot].lt = add(tree[rot].lt, lt, mid, pos, x);
else
tree[rot].rt = add(tree[rot].rt, mid + 1, rt, pos, x);
}
return rot;
}
int query(int *rot, const int num, const int lt, const int rt, const int k)
{
if (lt == rt)
return lt;
int tmp = 0;
for (int i = 0; i < num; i++)
tmp += tree[tree[rot[i]].lt].num;
int mid = (lt + rt) >> 1;
if (tmp >= k)
{
for (int i = 0; i < num; i++)
rot[i] = tree[rot[i]].lt;
return query(rot, num, lt, mid, k);
}
else
{
for (int i = 0; i < num; i++)
rot[i] = tree[rot[i]].rt;
return query(rot, num, mid + 1, rt, k - tmp);
}
}
}
namespace Segment_Tree
{
struct node
{
int rot;
}tree[N << 2];
int cnt, tmp[N];
void get_rot(const int rot, const int lt, const int rt, const int ls, const int rs)
{
if (!tree[rot].rot || (ls <= lt && rt <= rs))
{
if (tree[rot].rot)
tmp[cnt++] = tree[rot].rot;
return;
}
int mid = (lt + rt) >> 1;
if (ls <= mid)
get_rot(rot << 1, lt, mid, ls, rs);
if (rs > mid)
get_rot(rot << 1 | 1, mid + 1, rt, ls, rs);
}
void change(const int rot, const int lt, const int rt, const int pos, const int from, const int to)
{
using Inner_Segment_Tree::add;
if (from >= 0)
add(tree[rot].rot, 1, maxx, from, -1);
tree[rot].rot = add(tree[rot].rot, 1, maxx, to, 1);
if (lt == rt)
return;
int mid = (lt + rt) >> 1;
if (pos <= mid)
change(rot << 1, lt, mid, pos, from, to);
else
change(rot << 1 | 1, mid + 1, rt, pos, from, to);
}
int query(const int ls, const int rs, const int k)
{
cnt = 0;
get_rot(1, 1, n, ls, rs);
return Inner_Segment_Tree::query(tmp, cnt, 1, maxx, k);
}
}
int tmp[N << 1], cnt;
struct _opt
{
char opt;
int a, b, c;
}opt[N];
int work()
{
using Segment_Tree::query;
using Segment_Tree::change;
read(n), read(m);
for (int i = 1; i <= n; i++)
read(arr[i]), tmp[cnt++] = arr[i];
for (int i = 1; i <= m; i++)
{
read(opt[i].opt), read(opt[i].a), read(opt[i].b);
if (opt[i].opt == 'Q')
read(opt[i].c);
else
tmp[cnt++] = opt[i].b;
}
sort(tmp, tmp + cnt);
maxx = unique(tmp, tmp + cnt) - tmp;
for (int i = 1; i <= n; i++)
{
arr[i] = upper_bound(tmp, tmp + maxx, arr[i]) - tmp;
change(1, 1, n, i, -1, arr[i]);
}
for (int i = 1; i <= m; i++)
if (opt[i].opt == 'C')
opt[i].b = upper_bound(tmp, tmp + maxx, opt[i].b) - tmp;
for (int i = 1; i <= m; i++)
{
if (opt[i].opt == 'Q')
write(tmp[query(opt[i].a, opt[i].b, opt[i].c) - 1]), putchar('\n');
else
{
change(1, 1, n, opt[i].a, arr[opt[i].a], opt[i].b);
arr[opt[i].a] = opt[i].b;
}
}
return 0;
}
}
int main()
{
freopen("2617.in", "r", stdin);
return zyt::work();
}
【洛谷2617_BZOJ1901】Dynamic Rankings(树套树)的更多相关文章
- 洛谷P2617 Dynamic Rankings (主席树)
洛谷P2617 Dynamic Rankings 题目描述 给定一个含有n个数的序列a[1],a[2],a[3]--a[n],程序必须回答这样的询问:对于给定的i,j,k,在a[i],a[i+1],a ...
- 2018.07.01洛谷P2617 Dynamic Rankings(带修主席树)
P2617 Dynamic Rankings 题目描述 给定一个含有n个数的序列a[1],a[2],a[3]--a[n],程序必须回答这样的询问:对于给定的i,j,k,在a[i],a[i+1],a[i ...
- 洛谷 P2617 Dynamic Rankings || ZOJ - 2112
写的让人看不懂,仅留作笔记 静态主席树,相当于前缀和套(可持久化方法构建的)值域线段树. 建树方法:记录前缀和的各位置的线段树的root.先建一个"第0棵线段树",是完整的(不需要 ...
- 洛谷P2617 Dynamic Ranking(主席树,树套树,树状数组)
洛谷题目传送门 YCB巨佬对此题有详细的讲解.%YCB%请点这里 思路分析 不能套用静态主席树的方法了.因为的\(N\)个线段树相互纠缠,一旦改了一个点,整个主席树统统都要改一遍...... 话说我真 ...
- 洛谷P2617 Dynamic Rankings 主席树 单点修改 区间查询第 K 大
我们将线段树套在树状数组上,查询前预处理出所有要一起移动的节点编号,并在查询过程中一起将这些节点移到左右子树上. Code: #include<cstdio> #include<cs ...
- 洛谷 P2617 Dynamic Rankings 解题报告
P2617 Dynamic Rankings 题目描述 给定一个含有\(n\)个数的序列\(a[1],a[2],a[3],\dots,a[n]\),程序必须回答这样的询问:对于给定的\(i,j,k\) ...
- 洛谷P2617 Dynamic Rankings
带修主席树模板题 主席树的单点修改就是把前缀和(大概)的形式改成用树状数组维护,每个树状数组的元素都套了一个主席树(相当于每个数组的元素root[i]都是主席树,且这个主席树维护了(i - lowbi ...
- 洛谷$P2617\ Dynamic\ Rankings$ 整体二分
正解:整体二分 解题报告: 传送门$w$ 阿查询带修区间第$k$小不显然整体二分板子呗,,, 就考虑先按时间戳排序(,,,其实并不需要读入的时候就按着时间戳排的鸭$QwQ$ 每次二分出$mid$先把所 ...
- bzoj 1901: Zju2112 Dynamic Rankings(树套树)
1901: Zju2112 Dynamic Rankings 经典的带改动求区间第k小值问题 树套树模板,我是用的线段树套splay实现的,并且用的数组模拟的,所以可能空间略大,bzoj过了,zoj过 ...
随机推荐
- http post提交数组
方式一:@RequestParam方式 服务提供方用@RequestParam注解接收参数,参数类型为long数组: @ApiOperation(value = "***", ta ...
- start-all.sh 启动时报错解决方案
文件拥有者不是当前用户,或者文件权限没有修改权限 解决方法: sudo chmod 777 "文件名" 或者用 su root 登录,然后删除 再 exit Datanote服 ...
- Scala-LIST/Tuple/Map
环境: CentOS 6.3 LIST(列表) 代码: $ cat list.scala var mylist = List(1,2,3) println(mylist) var mylist1 = ...
- Hadoop HDFS NFS GateWay部署深入具体解释
目的:通过挂载的方式,能够相似訪问本地磁盘的方式一样的訪问Hadoop文件.简单.方便.快捷. 0.系统版本号&hadoop版本号 1)系统版本号 [root@WEB-W031 sbin]# ...
- C#高级编程五十二天----有序列表
有序列表 假设须要基于对全部集合排序,就能够使用SortedList<TKey,TValue>类.这个类依照键给元素排序.这个集合中的值和键都能够使用随意类型. 以下的样例创建了一个有序列 ...
- js中的封装、继承、多态
Javascript是一门解释型的语言,是基于对象的,并不是真正的面向对象的语言,对变量类型的应用也是宽松的,其实它同样可以模拟面向对象的功能: 1 function myfun1(){ 2 ...
- Akka并发编程——第五节:Actor模型(四)
本节主要内容: 1. 停止Actor 1. 停止Actor (1)通过ActorSystem.shutdown方法停止全部 Actor的执行 /* *停止Actor:ActorSystem.shutd ...
- swift 笔记 (二十一) —— 高级运算符
高级运算符 位运算符 按位取反: ~ 按位与运算: & 按位或运算: | 按位异或运算: ^ 按位左移运算: << 按位右移动算: >> 溢出运算符 自从swif ...
- 解决ubuntu中firefox浏览器总是提示找不到server的问题
这个情况在我机器上常常出现,并且时不时的给你出点问题.可是有些时候等一下就好了.或者把引擎换到百度的话它就又行得通了.. 被这个问题搞得非常烦.上网查了下说是防火墙啊之类的出问题.可是自己弄了后这个问 ...
- 2016/1/14 java生成指定范围的随机数
java生成指定范围的随机数 package edu.sjtu.erplab.io; import java.util.Random; public class RandomTest { public ...