【洛谷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过 ...
随机推荐
- cf 55D 数位dp 好题
/* 刚开始我考虑0的情况,想将他剔除就将lcmn设为-1,这样还要判断0和lcmn是-1的情况很麻烦而且但是一直出错 后来觉得不用管0的情况就行了,可以认为符合. 解:将lcmn离散化,因为1-9的 ...
- Linux下汇编语言学习笔记13 ---
这是17年暑假学习Linux汇编语言的笔记记录,参考书目为清华大学出版社 Jeff Duntemann著 梁晓辉译<汇编语言基于Linux环境>的书,喜欢看原版书的同学可以看<Ass ...
- mysql实现oracle序列
mysql实现oracle序列的方案1.建表,表结构为:drop table if exists sequence; create table sequence ( seq_name VARCHAR( ...
- 我的arcgis培训照片5
来自:http://www.cioiot.com/successview-528-1.html
- Dubbo-admin无法显示Group分组信息
背景: 在首次使用Dubbo的时候.我们可能都会使用Dubbo-admin来监控服务的提供者和消费者,可是在自己的生产者成功执行的时候.在Dubbo-admin却看不到不论什么信息.假设在确保代码的正 ...
- [TypeScript] Define Custom Type Guard Functions in TypeScript
One aspect of control flow based type analysis is that the TypeScript compiler narrows the type of a ...
- yarn之安装依赖包
安装依赖关系 yarn install用于安装项目的所有依赖项.依赖关系从您的项目package.json文件中检索,并存储在yarn.lock文件中. 开发包时,安装依赖关系最常见的是在 您刚刚检出 ...
- 使用bbed改动数据
bbed是一个强大的工具,同意我们绕过oracle直接从数据文件里改动相应的内容 ZBDBA@orcl11g>select * from emp; EMPNO ENAME JOB MGR HIR ...
- opencv基础笔记(1)
为了细致掌握程明明CVPR 2014 oral文章:BING: Binarized Normed Gradients for Objectness Estimation at 300fps的代码,的好 ...
- POJ 2562:Primary Arithmetic
Primary Arithmetic Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 11135 Accepted: 40 ...