【洛谷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过 ...
随机推荐
- Flask组件:flask-sqlalchemy & flask-script & flask-migrate
flask-sqlalchemy组件 项目目录结构: flask目录 # 项目名 |--- flaskdir |--- static # 静态文件 |--- templates # 模板 |--- m ...
- 好用的window命令
Nslookup-------IP地址侦测器 chkdsk-----Chkdsk磁盘检查 regedt32-------注册表编辑器 regedit----注册表 perfmon----计算机性能监测 ...
- 【ZJOI2017 Round1游记】
DAY0: 中午12点出发,下午5点到 酒店意外豪华 晚上和MG,LYY们定了个寿司套餐 没什么学习就睡觉了 DAY1: 听说RYZ在ZJ的OIer中影响颇深 讲STL的小哥真是对不住因为我是P党 D ...
- 从零开始写STL—容器—vector
从0开始写STL-容器-vector vector又称为动态数组,那么动态体现在哪里?vector和一般的数组又有什么区别?vector中各个函数的实现原理是怎样的,我们怎样使用会更高效? 以上内容我 ...
- Redux 中文文档
http://cn.redux.js.org/docs/introduction/Ecosystem.html
- golang中关闭http server
golange 开启http server 服务之后,怎么关闭呢? ------------------------------------------------------------------ ...
- php设计模式——模板模式
最近打算巩固,整理一下设计模式相关的内容.这篇是关于 ——模板模式! 原文:http://www.jb51.net/article/76052.htm ----------------------- ...
- Linux---有关dig命令的有用脚本
这里直接给出脚本以及运行的效果图,主要推断了一下cdn然后能够直接过滤url.默认就是dig +域名 +short. 脚本qdig(随便能够取一个名字)例如以下: #!/usr/bin/env bas ...
- EC2的维护更新-总结篇及有效经验分享
2014年10月11日 号,我们对不到10%的EC2实例的完毕了重新启动.来预防不论什么与Xen安全通报(XSA-108)相关的安全风险. 日之前都有义务遵守相关问题的保密要求.直到它被向公众公布. ...
- 一个最简单的Servlet实例
先在tomcat的webapps目录下,新建一目录,如test.然后,在test目录下建立WEB-INF为名的目录.这个必须有. 然后,在WEB-INF目录下建立classes目录.用以存储所用到的c ...