题目:

洛谷 2617

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(树套树)的更多相关文章

  1. 洛谷P2617 Dynamic Rankings (主席树)

    洛谷P2617 Dynamic Rankings 题目描述 给定一个含有n个数的序列a[1],a[2],a[3]--a[n],程序必须回答这样的询问:对于给定的i,j,k,在a[i],a[i+1],a ...

  2. 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 ...

  3. 洛谷 P2617 Dynamic Rankings || ZOJ - 2112

    写的让人看不懂,仅留作笔记 静态主席树,相当于前缀和套(可持久化方法构建的)值域线段树. 建树方法:记录前缀和的各位置的线段树的root.先建一个"第0棵线段树",是完整的(不需要 ...

  4. 洛谷P2617 Dynamic Ranking(主席树,树套树,树状数组)

    洛谷题目传送门 YCB巨佬对此题有详细的讲解.%YCB%请点这里 思路分析 不能套用静态主席树的方法了.因为的\(N\)个线段树相互纠缠,一旦改了一个点,整个主席树统统都要改一遍...... 话说我真 ...

  5. 洛谷P2617 Dynamic Rankings 主席树 单点修改 区间查询第 K 大

    我们将线段树套在树状数组上,查询前预处理出所有要一起移动的节点编号,并在查询过程中一起将这些节点移到左右子树上. Code: #include<cstdio> #include<cs ...

  6. 洛谷 P2617 Dynamic Rankings 解题报告

    P2617 Dynamic Rankings 题目描述 给定一个含有\(n\)个数的序列\(a[1],a[2],a[3],\dots,a[n]\),程序必须回答这样的询问:对于给定的\(i,j,k\) ...

  7. 洛谷P2617 Dynamic Rankings

    带修主席树模板题 主席树的单点修改就是把前缀和(大概)的形式改成用树状数组维护,每个树状数组的元素都套了一个主席树(相当于每个数组的元素root[i]都是主席树,且这个主席树维护了(i - lowbi ...

  8. 洛谷$P2617\ Dynamic\ Rankings$ 整体二分

    正解:整体二分 解题报告: 传送门$w$ 阿查询带修区间第$k$小不显然整体二分板子呗,,, 就考虑先按时间戳排序(,,,其实并不需要读入的时候就按着时间戳排的鸭$QwQ$ 每次二分出$mid$先把所 ...

  9. bzoj 1901: Zju2112 Dynamic Rankings(树套树)

    1901: Zju2112 Dynamic Rankings 经典的带改动求区间第k小值问题 树套树模板,我是用的线段树套splay实现的,并且用的数组模拟的,所以可能空间略大,bzoj过了,zoj过 ...

随机推荐

  1. [K/3Cloud] 在设计时复制已有表单菜单或菜单项快速建立菜单

    1.打开已有表单XML,找到FormAppearance的Menu节点,按Ctrl+C复制 <Appearances> <FormAppearance ElementType=&qu ...

  2. 基于HttpClient4.5.2实现的HttpClient工具类

    1.maven依赖: <dependency> <groupId>org.apache.commons</groupId> <artifactId>co ...

  3. Linux下汇编语言学习笔记67 ---

    这是17年暑假学习Linux汇编语言的笔记记录,参考书目为清华大学出版社 Jeff Duntemann著 梁晓辉译<汇编语言基于Linux环境>的书,喜欢看原版书的同学可以看<Ass ...

  4. poj——2367  Genealogical tree

    Genealogical tree Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 6025   Accepted: 3969 ...

  5. MongoDB小结27 - 聚合管道【$project】

    我们有这样的数据 { "_id" : 1, title: "abcdef", isbn: "6969696969", author: { l ...

  6. CSS头像右上角的讨厌红点

    就是这个讨厌的红点,如图: 说明: 1.主要用到position定位: 2.使用border-radius画圆角: 源码: <!DOCTYPE html> <html> < ...

  7. springMVC多数据源使用 跨库跨连接

    原文:http://blog.itpub.net/9399028/viewspace-2106641/ http://blog.csdn.net/a973893384/article/details/ ...

  8. 踩坑录-IDEA编辑器:找不到TomcatService或ApplicationServers----TomcatService使用指南

    一.找不到TomcatService或ApplicationServers Setp1. 检查IDEA版本 检查IDEA版本是否为Ultimate(终极版需要激活),Community(社区版免费无需 ...

  9. AutoCAD如何倒角 倒圆角 倒直角

    倒圆角:输f 再输r 再输入你想倒的半径,然后选相邻的两边倒直角:输chamfer 再输d 再输你想倒的距离,然后先相邻的两边 祝你成功

  10. bootstrap模态框出现或者消失的回调函数

    当某一模态框出现的时候就触发函数: $(".modal").on('show.bs.modal',function(){ if(vueObj){...}else{//如果vue对象 ...