题意:

给定一个区间,求这个区间第k小的数,支持单点修改。

思路:

动态主席树裸题。

我们先来回顾一下静态主席树的做法,对于数组中每一个位置都维护一棵权值线段树,该权值线段树保存的是区间 [1,x] 的信息。因此我想要求区间 [l,r] 之间第k大的时候,只需要将root[r]-root[l-1]就是维护区间 [l,r] 信息的权值线段树,因此就可以快速直接求出这个区间中第k大的元素是多少。

现在我们来看看单点修改的操作。

如果我现在要将a[pos]修改为x,那么最暴力的做法就是对于root[pos]~root[n]中的每一颗权值线段树都进行修改,即将a[pos]这个点的值减1,将x这个点的值+1。

最暴力的做法显然是无法通过此题的,因此我们可以想到有没有一种logn的方法,可以只修改logn个节点,就可以对于每一个线段树记录修改信息,于是我们想到了树状数组。

我们来回忆一下树状数组,每一个节点记录区间 [x-lowbit(x)+1, x] 的所有信息,因此当需要求[1,x]内维护的信息的时候,只需要从节点x出发,每次进行 x-=lowbit(x) 的操作,即可求出[1,x]内维护的所有信息。

每次对x节点进行修改的时候,只需要不断进行x+=lowbit(x)的操作,就可以访问到所有存储x节点信息的节点,因此实现了logn的查询。

因此本题也维护一个树状数组。

节点x维护的是区间 [x-lowbit(x)+1, x] 的权值线段树,因此当我需要访问 [l, r] 区间信息的时候,只需要将root[r]-root[l-1]+getsum(r)-getsum(l-1)这里面维护的便是区间 [l, r] 的权值线段树。

总结:

树状数组是一种很优秀的思想,他令每一个节点维护一个区间内的信息,于是在空间复杂度为n的情况下实现了logn的查询和修改操作,非常伟大!

代码:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define LOG1(x1,x2) cout << x1 << ": " << x2 << endl;
#define LOG2(x1,x2,y1,y2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << endl;
#define LOG3(x1,x2,y1,y2,z1,z2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << " , " << z1 << ": " << z2 << endl;
typedef long long ll;
typedef double db;
const int N = 6*1e4+100;
const int M = 32*N+100;
const db EPS = 1e-9;
using namespace std;int rt[N],root[N],ls[M],rs[M],sz,n,m,tot,h1,h2,L[N],R[N],sum[M],a[N],b[N];
struct Node{int op,xx,yy,k; //保存修改信息
}t[N];int find(ll x) {return (lower_bound(b+1,b+1+tot,x)-b);}
int lowbit(int x) {return x&(-x);}void update(int pre,int &now,int l,int r,int pos,int c){if(!now) now = ++sz;sum[now] = sum[pre]+c;if(l == r) return;int mid = (l+r)>>1;if(pos <= mid){rs[now] = rs[pre]; update(ls[pre],ls[now],l,mid,pos,c);}else{ls[now] = ls[pre]; update(rs[pre],rs[now],mid+1,r,pos,c);}
}int query(int pre,int now,int l,int r,int k){int sum1 = 0, sum2 = 0;if(l == r) return l;rep(i,1,h1) sum1 += sum[ls[L[i]]]; //左边树状数组节点累加值rep(i,1,h2) sum2 += sum[ls[R[i]]]; //右边树状数组节点累加值int temp = sum[ls[now]]-sum[ls[pre]]+sum2-sum1;int mid = (l+r)>>1;if(temp >= k){rep(i,1,h1) L[i] = ls[L[i]];rep(i,1,h2) R[i] = ls[R[i]];return query(ls[pre],ls[now],l,mid,k);}else{rep(i,1,h1) L[i] = rs[L[i]];rep(i,1,h2) R[i] = rs[R[i]];return query(rs[pre],rs[now],mid+1,r,k-temp);   }
}void init(){scanf("%d%d",&n,&m);rep(i,1,n){scanf("%d",&a[i]); b[++tot] = a[i];}rep(i,1,m){char op[10]; scanf("%s",op);if(op[0] == 'Q'){t[i].op = 1; scanf("%d%d%d",&t[i].xx,&t[i].yy,&t[i].k);}else{t[i].op = 2; scanf("%d%d",&t[i].xx,&t[i].yy);b[++tot] = t[i].yy;}}sort(b+1,b+1+tot);tot = unique(b+1,b+1+tot)-b-1;rep(i,1,n)update(rt[i-1],rt[i],1,tot,find(a[i]),1);
}void solve(){rep(i,1,m){if(t[i].op == 1){ //查询h1 = h2 = 0;for(int j = t[i].xx-1; j; j -= lowbit(j)) L[++h1] = root[j]; //记录树状数组要计算的节点for(int j = t[i].yy; j; j -= lowbit(j)) R[++h2] = root[j];int pos = query(rt[t[i].xx-1],rt[t[i].yy],1,tot,t[i].k);printf("%d\n",b[pos]);}else{ //修改int pos1 = find(a[t[i].xx]), pos2 = find(t[i].yy);for(int j = t[i].xx; j <= n; j += lowbit(j)) update(root[j],root[j],1,tot,pos1,-1);for(int j = t[i].xx; j <= n; j += lowbit(j)) update(root[j],root[j],1,tot,pos2,1);           a[t[i].xx] = t[i].yy;}}
}int main()
{int _; scanf("%d",&_);while(_--){init();solve();rep(i,0,sz) ls[i] = rs[i] = sum[i] = 0;rep(i,0,n) root[i] = rt[i] = 0;sz = 0, tot = 0;}return 0;
}

非递归版本:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#define rep(i,a,b) for(int i = a; i <= b; i++)
using namespace std;
const int N = 60010;int n,m,tot,cnt,idx; //tot记录节点数 cnt记录离散化数组
int a[N],dis[N]; //原数组 离散化数组
int root[N],lc[N*40],rc[N*40],sum[N*40]; //root为静态主席树的根节点
int s[N],use[N];
//s为动态主席树的根节点,use是用来存储更新时,沿着lowbit上升时经过的树
//s[i]代表一棵权值线段树,这颗权值线段树统计的是[i-lowbit(i)+1,i]的区间修改信息
struct Node{int kind;int l,r,k;
}que[10010];int build(int l,int r)
{int rt = ++tot;sum[rt] = 0;if(l != r){int mid = (l+r)>>1;lc[rt] = build(l,mid);rc[rt] = build(mid+1,r);}return rt;
}//以last这棵树为参照,新建一棵树,即新建的树有一部分节点共用last这棵树
int update(int last,int pos,int val)
{int rt = ++tot, tmp = rt;int l = 1, r = cnt;sum[rt] = sum[last]+val;while(l < r){int mid = (l+r)>>1;if(pos <= mid){lc[rt] = ++tot, rc[rt] = rc[last]; //对左右儿子赋值rt = lc[rt], last = lc[last]; //进入左儿子部分进行更新r = mid;}else{rc[rt] = ++tot, lc[rt] = lc[last];rt = rc[rt], last = rc[last];l = mid+1;}sum[rt] = sum[last]+val;}return tmp;
}int lowbit(int x)
{return x&(-x);
}void add(int x,int pos,int val)
{while(x <= n){s[x] = update(s[x],pos,val);x += lowbit(x);}
}int getSum(int x)
{int ret = 0;while(x > 0){ret += sum[lc[use[x]]];x -= lowbit(x);}return ret;
}int query(int left,int right,int k)
{int left_rt = root[left-1];int right_rt = root[right];int l = 1, r = cnt;for(int i = left-1; i ; i-= lowbit(i)) use[i] = s[i];//使用use数组的目的是将树状数组求和路径记录下来for(int i = right; i ; i -= lowbit(i)) use[i] = s[i]; //由于是从根节点往下走,一开始是s,后来是lc,所以需要开一个use数组while(l < r) //用循环模拟递归,减小常数{int mid = (l+r)>>1;int tmp = getSum(right)-getSum(left-1)+sum[lc[right_rt]]-sum[lc[left_rt]];if(tmp >= k){r = mid;for(int i = left-1; i ; i -= lowbit(i)) use[i] = lc[use[i]];for(int i = right; i ; i -= lowbit(i)) use[i] = lc[use[i]];left_rt = lc[left_rt];right_rt = lc[right_rt];}else{l = mid+1;k -= tmp;for(int i = left-1; i ; i -= lowbit(i)) use[i] = rc[use[i]];for(int i = right; i ; i -= lowbit(i)) use[i] = rc[use[i]];left_rt = rc[left_rt];right_rt = rc[right_rt];}}return l; //此处return l或者r 都是可以的
}int getID(int x) //二分查找这个数离散化之后的位置
{return lower_bound(dis+1,dis+1+cnt,x)-dis;
}int main()
{int T;scanf("%d",&T);while(T--){scanf("%d%d",&n,&m); //n个人,m条操作tot = cnt = idx = 0;rep(i,1,n){scanf("%d",&a[i]);dis[++cnt] = a[i]; //离散化数组}char op[10];//至于为什么要先把所有的修改的节点找出来,才进行建树,那是因为只有知道序列的范围才可以建树,无他//因此需要离线操作,主要原因是离散化rep(i,1,m){scanf("%s",op);if(op[0] == 'Q'){que[i].kind = 0;//查询[l,r]第k大scanf("%d%d%d",&que[i].l,&que[i].r,&que[i].k);}else{que[i].kind = 1;//将第x个点改为yscanf("%d%d",&que[i].l,&que[i].r);dis[++cnt] = que[i].r;}}sort(dis+1,dis+1+cnt);cnt = unique(dis+1,dis+1+cnt)-dis-1;//离散化root[0] = build(1,cnt);//建立空树rep(i,1,n)root[i] = update(root[i-1],getID(a[i]),1);//建立静态主席树rep(i,1,n)s[i] = root[0];//为每个树状数组根节点初始化rep(i,1,m){if(que[i].kind == 0)printf("%d\n",dis[query(que[i].l,que[i].r,que[i].k)]);else{add(que[i].l,getID(a[que[i].l]),-1);//先消除影响add(que[i].l,getID(que[i].r),1);//再新建影响a[que[i].l] = que[i].r;}}}return 0;
}

【动态主席树】ZOJ 2112【树状数组+主席树】相关推荐

  1. COGS-257-动态排名系统-树状数组+主席树

    描述 给定一个长度为N的已知序列A[i](1<=i<=N),要求维护这个序列,能够支持以下两种操作: 1.查询A[i],A[i+1],A[i+2],...,A[j](1<=i< ...

  2. D-query SPOJ - DQUERY(求区间不同数的个数)(树状数组||线段树+离散)(主席树+在线)

    English Vietnamese Given a sequence of n numbers a1, a2, -, an and a number of d-queries. A d-query ...

  3. 【bzoj3744】Gty的妹子序列 分块+树状数组+主席树

    题目描述 我早已习惯你不在身边, 人间四月天 寂寞断了弦. 回望身后蓝天, 跟再见说再见-- 某天,蒟蒻Autumn发现了从 Gty的妹子树(bzoj3720) 上掉落下来了许多妹子,他发现 她们排成 ...

  4. 线。段。树--树状数组-主席树

    简单了解一下线段树 以前写过的内容,搬运过来 线段树的应用场景:满足区间加法性质且多次查询,什么是区间加法性质,比如最大值,求和,树状数组.线段树.主席树依次. 线段树框架:建树--查询--更新... ...

  5. 树状数组(树状数组的基本用法与操作)

    什么是树状数组?树状数组简单的来说就是将一个数组模拟树形结构. 树状数组有什么用?树状数组可以将求和的操作从O(n)操作简化为O(logn). 如图所示,横线下方为a数组表示为初试数据:上方为数组c, ...

  6. ZOJ 2112 Dynamic Rankings(主席树-动态第k大)

    Description 给出一个长度为n的序列a,两种操作 C x v:将第x个元素的值改成v Q l r k:查询区间[l,r]中第k大的元素 Input 第一行为一个整数t表示用例组数,每组用例第 ...

  7. HDU 3333 Turing Tree(树状数组/主席树)

    题意 给定一个长度为 \(n​\) 的序列,\(m​\) 个查询,每次查询区间 \([L,R]​\) 范围内不同元素的和. \(1\leq T \leq 10\) \(1 \leq n\leq 300 ...

  8. hdu 4417 Super Mario 树状数组||主席树

    Super Mario Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Prob ...

  9. 洛谷 - P2163 [SHOI2007]园丁的烦恼(不带修二维数点-树状数组/主席树)

    题目链接:点击查看 题目大意:二维平面坐标系中给出 nnn 个坐标点,然后是 mmm 次询问,每次询问需要回答一个闭合矩阵中有多少个点 题目分析:想挂树套树来着,但是复杂度有点大.本题不带修且可以离线 ...

  10. [NOI Online 2022 提高组] 丹钓战(单调栈 + 树状数组 / 主席树)

    problem luogu-P8251 solution 按照题意模拟单调栈. 求出对于 iii 而言,当时单调栈的栈顶元素记为 pip_ipi​. 如果到 iii 时,栈顶已经为 pip_ipi​ ...

最新文章

  1. 整系数多项式的整除平移不变性
  2. access转换成oracle,Access转Oracle工具
  3. 从全职高手开始的系统_动画全职高手第二季热血回归,腾讯视频的国漫IP全链路开发之道...
  4. Java程序通过批处理文件定时执行
  5. 数据源管理 | 搜索引擎框架,ElasticSearch集群模式
  6. 单片机断电后不保存程序_法兰购买到货后直接入库保存?不不不,还需要做一件事情...
  7. [C++] Value Categories
  8. Julia : where与类型限定
  9. java实现5 4 3 2 1递归_递归及递归的使用
  10. 基于JAVA的超市管理系统计算机毕业论文
  11. mysql嵌套查询效率低_mysql的嵌套查询效率很低
  12. 【字符串】13. 罗马数字转整数
  13. win7右键显示隐藏文件及扩展名
  14. 由于word打字卡顿、延迟,怎么重新安装office
  15. 手把手带你开发一个批量下载资源的谷歌浏览器扩展
  16. 动图如何在线制作?教你一键在线制作动图
  17. Snowflake Snow Snowflakes(哈希表的应用)
  18. js设置css色相旋转_色相旋转颜色方案是否保留了对色盲友好的能力?
  19. 微信小程序场景值列表map格式
  20. 学习太极创客 — MQTT 第二章(七)ESP8266 MQTT 遗嘱应用

热门文章

  1. 运营商管道的精细化运营之路
  2. 大连理工大学计算机组织与结构实验,大连理工大学计算机系统结构实验-实验四.doc...
  3. vue变量赋值到html,jsvue data变量相互赋值后被实时同步的解决步骤js大全-js开发...
  4. 2020年不能启动win7_包装车间2020年大修正式启动
  5. android示例程序剖析之记事本,Android实现记事本项目完整实例,附源代码
  6. A1055[The World's Richest]好题
  7. java去除以张开头的人名_写出java8实现对ListUser中的username字段过滤出不等于张三的数据...
  8. 前端保存之前输入的值_前端基础进阶(一):内存空间详细图解
  9. 两台服务器安装redis集群_高性能分布式存储服务Minio安装配置入门
  10. linux汇编预处理,Linux程序在预处理、编译、汇编、链接、运行步骤的作用