少年,想学带修改主席树吗 | BZOJ1901 带修改区间第k小


有一道题(BZOJ 1901)是这样的:n个数,m个询问,询问有两种:修改某个数/询问区间第k小。

不带修改的区间第k小用主席树很好写,不会的同学可以看一下这个

加上修改怎么做呢?我们可以用数学老师成天讲的类比思想:

可以发现,不修改的区间k小问题中,每加入一个原序列中的数,对应的主席树在上一个的基础上进行修改,而查询的时候用右端点主席树减去左端点左边的主席树。这样的操作就像是维护前缀和:每次加入一个元素的时候,sum[i] = sum[i - 1] + a[i];询问的时候,则是sum[r] - sum[l - 1]。

sum数组可以用来不带修改求前缀和,那么假如我们要求带修改的前缀和呢?树状数组可以做到。每次在位置p加入一个元素x的时候,对树状数组中每个 {p, p + (p & -p), ...} 都加上x;询问前缀和的时候,则是求树状数组中每个 {p, p - (p & -p), ...} 的和。

现在我们来看看能否将树状数组和主席树结合起来,实现动态修改。

一开始,树状数组的每个位置都对应着一棵主席树——虽然实际上它们对应的都是同一棵空空荡荡的主席树。

然后我们该往里面加数了:当在位置p加入一个数x的时候,对树状数组中位置 {p, p + (p & -p), ...} 上的主席树都进行“加入数x”的操作,也就是把数x对应的位置++。

然后可以处理询问了。对于查询,可以采用非递归的方式:维护两个cur数组,分别记录左端点(的左边)对应的主席树 {l, l - (l & -l), ...} 上的当前节点和右端点对应主席树 {r, r - (r & -r), ...} 上的当前节点。每次对两个cur数组上记录的节点的data求和,然后相减,可以得到区间内的data。这个操作实际上就是树状数组求前缀和的操作。

询问的代码如下(写得比较臃肿……但是意思应该是比较清楚的了):

int query(int ql, int qr, int k){
int l = 1, r = idx;
for(int p = ql; p; p -= p & -p) cur1[p] = root[p];
for(int p = qr; p; p -= p & -p) cur2[p] = root[p];
while(l < r){
int mid = (l + r) >> 1, sum1 = 0, sum2 = 0;
for(int p = ql; p; p -= p & -p) sum1 += data[ls[cur1[p]]];
for(int p = qr; p; p -= p & -p) sum2 += data[ls[cur2[p]]];
if(sum2 - sum1 >= k){
for(int p = ql; p; p -= p & -p) cur1[p] = ls[cur1[p]];
for(int p = qr; p; p -= p & -p) cur2[p] = ls[cur2[p]];
r = mid;
}
else{
l = mid + 1, k -= sum2 - sum1;
for(int p = ql; p; p -= p & -p) cur1[p] = rs[cur1[p]];
for(int p = qr; p; p -= p & -p) cur2[p] = rs[cur2[p]];
}
}
return lst[l];
}

那么对于修改操作呢?当然是和初始化时加入每个数时一样,对于树状数组中每个 {p, p + (p & -p), ...} 位置上的主席树都进行修改咯。

void change(int old, int &k, int l, int r, int p, int x){
k = ++tot;
data[k] = data[old] + x, ls[k] = ls[old], rs[k] = rs[old];
if(l == r) return;
int mid = (l + r) >> 1;
if(p <= mid) change(ls[old], ls[k], l, mid, p, x);
else change(rs[old], rs[k], mid + 1, r, p, x);
}
void add(int p, int num, int x){
while(p <= n) change(root[p], root[p], 1, idx, num, x), p += p & -p;
}

那么这道题就做完啦。

下面是完整的代码,加上近三十行的读入优化后也只有100行,比起学长让我近期写的各种树套树……应该算是比较短小好写的吧。

树状数组天下第一!

#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#define space putchar(' ')
#define enter putchar('\n')
using namespace std;
typedef long long ll;
template <class T>
void read(T &x){
char c;
bool op = 0;
while(c = getchar(), c < '0' || c > '9')
if(c == '-') op = 1;
x = c - '0';
while(c = getchar(), c >= '0' && c <= '9')
x = x * 10 + c - '0';
if(op) x = -x;
}
template <class T>
void write(T x){
if(x < 0) putchar('-'), x = -x;
if(x >= 10) write(x / 10);
putchar('0' + x % 10);
}
const int N = 100005, M = 6000005;
int n, m, a[N], lst[N], idx;
int tot, root[N], data[M], ls[M], rs[M], cur1[N], cur2[N];
int qtype[N], q1[N], q2[N], q3[N];
void build(int &k, int l, int r){
k = ++tot;
if(l == r) return;
int mid = (l + r) >> 1;
build(ls[k], l, mid);
build(rs[k], mid + 1, r);
}
void change(int old, int &k, int l, int r, int p, int x){
k = ++tot;
data[k] = data[old] + x, ls[k] = ls[old], rs[k] = rs[old];
if(l == r) return;
int mid = (l + r) >> 1;
if(p <= mid) change(ls[old], ls[k], l, mid, p, x);
else change(rs[old], rs[k], mid + 1, r, p, x);
}
void add(int p, int num, int x){
while(p <= n) change(root[p], root[p], 1, idx, num, x), p += p & -p;
}
int query(int ql, int qr, int k){
int l = 1, r = idx;
for(int p = ql; p; p -= p & -p) cur1[p] = root[p];
for(int p = qr; p; p -= p & -p) cur2[p] = root[p];
while(l < r){
int mid = (l + r) >> 1, sum1 = 0, sum2 = 0;
for(int p = ql; p; p -= p & -p) sum1 += data[ls[cur1[p]]];
for(int p = qr; p; p -= p & -p) sum2 += data[ls[cur2[p]]];
if(sum2 - sum1 >= k){
for(int p = ql; p; p -= p & -p) cur1[p] = ls[cur1[p]];
for(int p = qr; p; p -= p & -p) cur2[p] = ls[cur2[p]];
r = mid;
}
else{
l = mid + 1, k -= sum2 - sum1;
for(int p = ql; p; p -= p & -p) cur1[p] = rs[cur1[p]];
for(int p = qr; p; p -= p & -p) cur2[p] = rs[cur2[p]];
}
}
return lst[l];
}
int getpos(int x){
return lower_bound(lst + 1, lst + idx + 1, x) - lst;
}
bool isQ(){
char c;
while(c = getchar(), c != 'Q' && c != 'C');
return c == 'Q';
}
int main(){
read(n), read(m), idx = n;
for(int i = 1; i <= n; i++)
read(a[i]), lst[i] = a[i];
for(int i = 1; i <= m; i++){
qtype[i] = isQ(), read(q1[i]), read(q2[i]);
if(qtype[i]) read(q3[i]);
else lst[++idx] = q2[i];
}
sort(lst + 1, lst + idx + 1);
idx = unique(lst + 1, lst + idx + 1) - lst - 1;
build(root[0], 1, idx);
for(int i = 1; i <= n; i++) root[i] = root[0];
for(int i = 1; i <= n; i++) add(i, getpos(a[i]), 1);
for(int i = 1; i <= m; i++){
if(qtype[i]) write(query(q1[i] - 1, q2[i], q3[i])), enter;
else{
add(q1[i], getpos(a[q1[i]]), -1);
a[q1[i]] = q2[i];
add(q1[i], getpos(a[q1[i]]), 1);
}
}
return 0;
}

少年,想学带修改主席树吗 | BZOJ1901 带修改区间第k小的更多相关文章

  1. 洛谷3834 hdu2665主席树模板,动态查询区间第k小

    题目链接:https://www.luogu.com.cn/problem/P3834 对于区间查询第k小的问题,在区间数量达到5e5的时候是难以用朴素数据结构实现的,这时候主席树就应运而生了,主席树 ...

  2. 主席树学习笔记(静态区间第k大)

    题目背景 这是个非常经典的主席树入门题——静态区间第K小 数据已经过加强,请使用主席树.同时请注意常数优化 题目描述 如题,给定N个整数构成的序列,将对于指定的闭区间查询其区间内的第K小值. 输入输出 ...

  3. 主席树的各类模板(区间第k大数【动,静】,区间不同数的个数,区间<=k的个数)

    取板粗   好东西来的 1.(HDOJ2665)http://acm.hdu.edu.cn/showproblem.php?pid=2665 (POJ2104)http://poj.org/probl ...

  4. BZOJ1901 Dynamic Rankings|带修主席树

    题目链接:戳我 其实我并不会做,于是看了题解 我们都知道主席树是利用前缀和记录历史版本来搞区间K大的一种数据结构.不过一般的主席树只能搞定静态区间第K大.如果带修怎么办呢? 想一下...单点修改+区间 ...

  5. [BZOJ3295] [Cqoi2011]动态逆序对(带修改主席树)

    题目描述 对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数.给1到n的一个排列,按照某种顺序依次删除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序 ...

  6. 【bzoj1901】dynamic ranking(带修改主席树/树套树)

    题面地址(权限题) 不用权限题的地址 首先说说怎么搞带修改主席树? 回忆一般的kth问题,我们的主席树求的是前缀和,这样我们在目标区间的左右端点的主席树差分下就能求出kth. 那么我们如何支持修改操作 ...

  7. BZOJ 1901: Zju2112 Dynamic Rankings | 带修改主席树

    题目: emmmm是个权限题 题解: 带修改主席树的板子题,核心思想是用树状数组维护动态前缀和的性质来支持修改 修改的时候修改类似树状数组一样进行logn个Insert 查询的时候同理,树状数组的方法 ...

  8. BZOJ 1146: [CTSC2008]网络管理Network 带修改主席树_树套树_DFS序

    Description M公司是一个非常庞大的跨国公司,在许多国家都设有它的下属分支机构或部门.为了让分布在世界各地的N个 部门之间协同工作,公司搭建了一个连接整个公司的通信网络.该网络的结构由N个路 ...

  9. 【bzoj1901】dynamic ranking(带修改主席树)

    传送门(权限) 传送门(非权限) 花了一晚上总算把代码调好了……才知道待修改主席树怎么操作…… 然而还是一知半解orz…… 先说说我的理解吧 我们一般建主席树的时候都是直接在序列上建的 但是如果有修改 ...

随机推荐

  1. RabbitMQ入门:总结

    随着上一篇博文的发布,RabbitMQ的基础内容我也学习完了,RabbitMQ入门系列的博客跟着收官了,以后有机会的话再写一些在实战中的应用分享,多谢大家一直以来的支持和认可. RabbitMQ入门系 ...

  2. SQL Server上DBLINK的创建,其实很简单!(上)

    今天给大家来分享一下跨服务器操作数据库,还是以SQL Server的管理工具(SSMS)为平台进行操作. 什么是跨服务器操作? 跨服务器操作就是可以在本地连接到远程服务器上的数据库,可以在对方的数据库 ...

  3. DevOps on AWS之OpsWorks初体验

    AWS OpsWorks 是一款配置管理服务,提供 Chef 和 Puppet 的托管EC2虚拟机实例.Chef 和 Puppet 是自动化平台,允许用户使用代码来自动配置服务器.用户借助OpsWor ...

  4. 简单理解DNS解析流程(一)

    0x0 简单理解dns DNS服务器里存着一张表 表中放着域名和IP地址,域名和IP地址以映射关系保存,即一对一 浏览器访问某个域名,实际上是访问它的ip地址 所以浏览器需要知道域名对应的ip地址 如 ...

  5. 看oracle的sid

    ps -ef|grep pmon 可以从进程名字里看到 也可以通过 sqlplus / as sysdbashow parameter instance_name

  6. Linux读书笔记第一、二章

    第一章    Linux内核简介 1.1Unix历史 Unix特点:1.很简洁 2.所有东西都被当成文件对待 3.Unix内核和相关的系统工具软件都是用C语言编写而成 4.进程创建非常迅速 1.2追寻 ...

  7. 冲刺One之站立会议2

    在确定了总体目标之后,我们先决定了实现的具体功能,包括一个登陆界面,一个聊天室的主界面和服务器端的内容.我们今天完成了一小部分内容,把每个内容的主体框架搭建了起来. 效果如下图所示: 燃尽图2

  8. WPF四则运算《《《《《策略模式

    设计思路:           因为之前没有用过WPF,听说和window窗体语法类似,就想着仿照之前的Window窗体做的,首先用三个textbox存储数据,添加一个comboBox,利用索引选择运 ...

  9. android assets下rar文件解压到sd卡

    参考的 http://hzy3774.iteye.com/blog/1704419   不过只能解压zip文件  最多也就能解压1M多把 ,我1.5M的可以,4M的不行 还有...之前傻逼的把raw和 ...

  10. ns-3 可视化模拟 (一) PyViz

    PyViz 个人觉得这个的使用简单. (1)首先安装 这是ubuntu下的 sudo apt-get install python-dev python-pygraphviz python-kiwi ...