【洛谷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过 ...
随机推荐
- 【spring boot 系列】spring data jpa 全面解析(实践 + 源码分析)
前言 本文将从示例.原理.应用3个方面介绍spring data jpa. 以下分析基于spring boot 2.0 + spring 5.0.4版本源码 概述 JPA是什么? JPA (Java ...
- android开发里跳过的坑——图片文件上传失败
使用的apache的httpclient的jar包,做的http图片上传,上传时,服务器总返文件格式不对.后来发现,是由于在创建FileBody时,使用了默认的ContentType引起的.所以服务器 ...
- tyvj1031 热浪
背景 USACO OCT09 9TH 描述 德克萨斯纯朴的民眾们这个夏天正在遭受巨大的热浪!!!他们的德克萨斯长角牛吃起来不错,可是他们并不是很擅长生產富含奶油的乳製品.Farmer John此时以先 ...
- [bzoj2287][poj Challenge]消失之物_背包dp_容斥原理
消失之物 bzoj-2287 Poj Challenge 题目大意:给定$n$个物品,第$i$个物品的权值为$W_i$.记$Count(x,i)$为第$i$个物品不允许使用的情况下拿到重量为$x$的方 ...
- Spring MVC中的拦截器/过滤器HandlerInterceptorAdapter的使用
一般情况下,对来自浏览器的请求的拦截,是利用Filter实现的 而在Spring中,基于Filter这种方式可以实现Bean预处理.后处理. 比如注入FilterRegistrationBean,然后 ...
- Servlet的HTTP状态码
以下内容引用自http://wiki.jikexueyuan.com/project/servlet/http-status-codes.html: HTTP请求的格式和HTTP响应消息的格式是相似的 ...
- u启动为苹果笔记本重装win7系统教程
准备更换系统的苹果笔记本一台! 上述需要准备的东西均准备好以后我们就开始今天的教程了!! 首先,将已经制作好启动盘的u启动u盘插入到苹果笔记本上的usb插口,然后开机! 由于苹果笔记本电脑 ...
- json序列化后的是字符串,不是二进制。是字符串!!!确定不是二进制!!!
1.现有一个自定义对象需要储存到sql数据库中去.这个对象里面属性很多,甚至包含一些元素量打到几十万的List集合属性.本人试着使用JSON序列化这个对象,储存到数据库,报maxjsonlength超 ...
- (6)文本挖掘(三)——文本特征TFIDF权重计算及文本向量空间VSM表示
建立文本数据数学描写叙述的过程分为三个步骤:文本预处理.建立向量空间模型和优化文本向量. 文本预处理主要採用分词.停用词过滤等技术将原始的文本字符串转化为词条串或者特点的符号串.文本预处理之后,每个文 ...
- Cocos2dx使用ios内支付IAP具体流程-白白
今天总结了一下cocos2d-x使用ios内支付iap的具体流程,封装好了调用接口,代码与具体说明在此 http://download.csdn.net/detail/u010229677/81566 ...