Dynamic Rankings || 动态/静态区间第k小(主席树)
JYF大佬说,一星期要写很多篇博客才会有人看
但是我做题没有那么快啊QwQ
Part1 写在前面
区间第K小问题一直是主席树经典题=w=今天的重点是动态区间第K小问题。静态问题要求查询一个区间内的第k小的值(可重),动态问题还要求支持单点修改操作。
这个问题也可以用线段树+Splay/整体二分解决,然而那些对蒟蒻来说都太难辣QwQ,这里给一个XZY大佬的整体二分的讲解传送门
我们的做法是主席树+树状数组,如果有不会主席树的同学可以看我之前写的博客=w=,由于静态问题是动态问题的基础,所以我会先讲静态的怎么做,如果你已经对静态区间第k小问题了如指掌的话就可以跳过Part2直接看重点啦=w=
Part2 静态区间第k小
这个其实很简单,我们的每一棵主席树都是一棵权值线段树,第 $ i $ 棵主席树的每个叶子节点都存储对应的(离散化后的)值在序列前缀 $ [1,i] $ 中出现的次数,例如第 $ i $ 棵主席树的 $ j $ 个叶子节点就存储数值 $ hash[j] $ 在区间 $ [1,i] $ 中出现的次数。内部节点存储的则是两个儿子的值的和,例如表示区间 $ [l,r] $ 的内部节点存储的是 $ hash[l到r] $ 的所有数在序列 $ [1,i] $ 中出现的次数总和。
建树时,我们将序列的值离散化,主席树的时间维表示原序列坐标,从左到右依次插入。如果要查询前缀 $ [1,r] $ 中的第k小,就相当于在第 $ r $ 棵主席树上进行一个类似于splay的kth操作:看看当前节点 $ [l,r] $ 的左儿子的值 $ size $ ,如果大于等于k,说明当前区间第k小为 $ [l,mid] $ 中的第k小,反之则为 $ [mid+1,r] $ 中的第(k-size)小。这样不停地递归子区间,最后我们会来到一个叶节点,它就是我们要找的第k小了。那么,如果要查询区间 $ [l,r] $ 中的第k小的话,只需同时在第 $ r $ 与第 $ l-1 $ 棵主席树上进行kth,将 $ size $ 值相减后与k比较即可。
这么说可能不太直观,来一段代码:
void insert(int l,int r,int &x,int y,int tar)//x:当前位置i代表的root,y:i-1,tar:a[i]离散化后的位置
{
x=++cnt,node[x]=node[y],++node[x].x;
if(l==r)return;
int m=(l+r)>>1;
if(tar<=m)insert(l,m,node[x].l,node[y].l,tar);
else insert(m+1,r,node[x].r,node[y].r,tar);
}
int query(int l,int r,int x,int y,int size)//size即为第k小的k,x、y为两棵主席树当前遍历到的节点
{
if(l==r)return l;
int m=(l+r)>>1,tmp=node[node[y].l].x-node[node[x].l].x;
if(tmp>=size)return query(l,m,node[x].l,node[y].l,size);
else return query(m+1,r,node[x].r,node[y].r,size-tmp);
}
Part3 动态区间第k小
请确保你已经完全掌握了静态区间第k小问题,推荐SPOJ的COT这一题作为练习QwQ
在静态问题中,我们所建出来的主席树(们)所表示的区间长这样:
如果要修改原数组上的一个点,我们就必须一连修改包含它的n棵主席树,一次修改的复杂度就达到了 $ O(nlog_2n) $ 。我们要想办法使得一次修改不需要改那么多树。
emmmm...
查询前缀和,一次修改 $ log_2n $ 个节点,那岂不是。。。
树状数组:没错,就是我!
如果我们像这样划分每棵主席树所代表的区间:
显然,原序列的一个点最多被包含在 $ log_2n $ 棵主席树中,我们要查询区间 $ [l,r] $ 时则需要计算出 $ l-1 $ 与 $ r $ 的前缀和再相减即可。这两个操作的复杂度都变成了 $ O(long^2_2n) $ 。而且,树状数组编程复杂度极低,我们不需要真的写一个数据结构,只需要在主席树中运用lowbit函数就行了。
注意,这种划分方式使得每一棵主席树之间没有了公共部分,它们其实已经不能称之为主席树了,不如说是很多棵动态开点的线段树放在一起。然而,为了节省空间与时间(ZOJ上空间限制为丧心病狂的32MB),我们可以初始化一棵静态的主席树,只用套了树状数组的主席树来维护修改。其他的细节就看代码吧。
代码(ZOJ可AC):
#include <cstdio>
#include <cstring>
#include <abstergo>
#define R register
using namespace std;
const int MAXN=50050;
const int inf=0x3f3f3f3f;
int a[MAXN*2],X[MAXN*2],top;
int n;
template<class T>void read(T &x)
{
x=0;int ff=0;char ch=getchar();
while(ch<'0'||ch>'9'){ff|=(ch=='-');ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x=ff?-x:x;
}
void readc(char &ch)
{
ch=getchar();
while(ch<'A'||ch>'Z')ch=getchar();
}
struct asks
{
char ch;
int t1,t2,t3;
}ask[MAXN];//存储询问
inline int lowbit(int x)
{return x&(-x);}
int find(int x)
{return lower_bound(X+1,X+1+top,x)-X;}
class CMT
{
private:
int root1[MAXN*2],root[MAXN*2],bitr[50],bitl[50],cnt;
struct CMT_node
{
int l,r,x;
}node[MAXN*50];
void insert(int l,int r,int &x,int y,int tar,int del)
{
if(!x||x==y)x=++cnt,node[x]=node[y];
node[x].x+=del;
if(l==r)return;
int m=(l+r)>>1;
if(tar<=m)insert(l,m,node[x].l,node[y].l,tar,del);
else insert(m+1,r,node[x].r,node[x].r,tar,del);
}
int query(int l,int r,int x,int y,int size,int len1,int len2)
{
if(l==r)return l;
int m=(l+r)>>1,tmp;
tmp=node[node[y].l].x-node[node[x].l].x;
for(int i=1;i<=len1;++i)tmp+=node[node[bitr[i]].l].x;
for(int i=1;i<=len2;++i)tmp-=node[node[bitl[i]].l].x;
if(tmp>=size)
{
for(int i=1;i<=len1;++i)bitr[i]=node[bitr[i]].l;
for(int i=1;i<=len2;++i)bitl[i]=node[bitl[i]].l;
return query(l,m,node[x].l,node[y].l,size,len1,len2);
}
else
{
for(int i=1;i<=len1;++i)bitr[i]=node[bitr[i]].r;
for(int i=1;i<=len2;++i)bitl[i]=node[bitl[i]].r;
return query(m+1,r,node[x].r,node[y].r,size-tmp,len1,len2);
}
}
public:
void init()
{
cnt=0;
memset(root1,0,sizeof(root1));
memset(root,0,sizeof(root));
for(int i=1;i<=n;++i)
insert(1,top,root1[i],root1[i-1],find(a[i]),1);
}
void change(int tar,int num)
{
int r=find(num),l=find(a[tar]);
a[tar]=num;
while(tar<=n)
{
insert(1,top,root[tar],0,r,1);
insert(1,top,root[tar],0,l,-1);
tar+=lowbit(tar);
}
}
int getkth(int l,int r,int x)
{
int len1=0,len2=0;
int ll=l-1,rr=r;
while(rr)bitr[++len1]=root[rr],rr-=lowbit(rr);//用两个数组存储查找时需要用到的主席树的编号
while(ll)bitl[++len2]=root[ll],ll-=lowbit(ll);
return query(1,top,root1[l-1],root1[r],x,len1,len2);
}
}cmt;
int main()
{
int T;
read(T);
while(T--)
{
int m;
read(n),read(m);
for(R int i=1;i<=n;++i)
read(a[i]),X[i]=a[i];//离散化
for(R int i=1;i<=m;++i)
{
readc(ask[i].ch);
if(ask[i].ch=='Q')
{
read(ask[i].t1),read(ask[i].t2),read(ask[i].t3);
X[i+n]=inf;
}
else
{
read(ask[i].t1),read(ask[i].t2);
X[i+n]=ask[i].t2;
}
}
sort(X+1,X+1+n+m);
X[0]=-inf;top=0;
for(R int i=1;i<=n+m;++i)
if(X[i]!=X[i-1])X[++top]=X[i];
cmt.init();
for(R int i=1;i<=m;++i)
{
if(ask[i].ch=='Q')
printf("%d\n",X[cmt.getkth(ask[i].t1,ask[i].t2,ask[i].t3)]);
else
cmt.change(ask[i].t1,ask[i].t2);
}
}
return 0;
}
最后祝你:身体健康。再见。
Dynamic Rankings || 动态/静态区间第k小(主席树)的更多相关文章
- POJ2104 K-th Number —— 静态区间第k小
题目链接:http://poj.org/problem?id=2104 K-th Number Time Limit: 20000MS Memory Limit: 65536K Total Sub ...
- Dynamic Rankings——带修改区间第k大
三种做法:1.整体二分: 二分mid 考虑小于mid的修改的影响 但是大于mid的修改可能会干掉小于mid的一些值 所以额外把一个修改变成一个值的删除和一个值的添加 这样就相互独立了! 整体二分,树状 ...
- P2617 Dynamic Rankings(待修改区间第k大)
题目链接:https://www.luogu.org/problemnew/show/P2617 题目: 题目描述 给定一个含有n个数的序列a[1],a[2],a[3]……a[n],程序必须回答这样的 ...
- 洛谷3834 hdu2665主席树模板,动态查询区间第k小
题目链接:https://www.luogu.com.cn/problem/P3834 对于区间查询第k小的问题,在区间数量达到5e5的时候是难以用朴素数据结构实现的,这时候主席树就应运而生了,主席树 ...
- POJ2104&&HDU2665(静态区间第K小)
题目大意 给定一个有N个数字的序列,然后又m个查询,形式如下: l r k 要求你返回区间[l,r]第k小的数是哪个 题解 终于弄懂主席树是个啥东西了,O(∩_∩)O~~,这题正是主席树的裸题,主席树 ...
- [luogu3834]静态区间第k小【主席树】
传送门:https://www.luogu.org/problemnew/show/P3834 题目描述 如题,给定N个整数构成的序列,将对于指定的闭区间查询其区间内的第K小值. 分析 很多人都说是用 ...
- 静态区间第K小(整体二分、主席树)
题目链接 题解 主席树入门题 但是这里给出整体二分解法 整体二分顾名思义是把所有操作放在一起二分 想想,如果求\([1-n]\)的第\(k\)小怎么二分求得? 我们可以二分答案\(k\), \(O(n ...
- 主席树(可持久化线段树)静态区间第K小
传送门主席树 #include <bits/stdc++.h> #define int long long using namespace std; const int maxn=2e5+ ...
- 洛谷.3834.[模板]可持久化线段树(主席树 静态区间第k小)
题目链接 //离散化后范围1~cnt不要错 #include<cstdio> #include<cctype> #include<algorithm> //#def ...
随机推荐
- P2824 [HEOI2016/TJOI2016]排序
题面 这是一道非常巧妙的线段树的题 我们会发现维护\(1 \sim n\)的序列非常困难,但如果我们维护\(01\)序列的的顺序,就非常容易了 但是我们怎么能把这道题变成维护\(01\)序列的顺序呢? ...
- Treasure Exploration POJ - 2594(最小边覆盖)
因为是路 所以 如果 1——3 2——3 3——4 3——5 则 1——4 1——5 2——4 2——5 都是是合法的 又因为机器人是可以相遇的 所以 我们把所有的点 分别放在 ...
- hadoop2相对hadoop1有非常重大的改进
hadoop2相对hadoop1有非常重大的改进. 下面看一下在HDFS和MapReduce方面的改进: HDFS Federation(HDFS联邦)federation-background[1] ...
- 造成ORA-01843 无效的月份 的一些原因
1) 当我们在一个中文环境的客户端使用如下sql语句INSERT INTO "temptable" ( DELIVER_DATE ) VALUES (TO_DATE('27-Jun ...
- gdb调试遇到的问题
解决方法:http://stackoverflow.com/questions/31062010/ubuntu-14-04-gcc-4-8-4-gdb-pretty-printing-doesnt-w ...
- Linux 之 crontab 使用
定时任务 任务调度的crond常驻命令crond 是linux用来定期执行程序的命令.当安装完成操作系统之后,默认便会启动此任务调度命令.crond命令每分锺会定期检查是否有要执行的工作,如果有要执行 ...
- Scala进阶之路-高级数据类型之数组的使用
Scala进阶之路-高级数据类型之数组的使用 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.数组的初始化方式 1>.长度不可变数组Array 注意:顾名思义,长度不可变数 ...
- Dubbo学习笔记7:Dubbo的集群容错与负载均衡策略
Dubbo的集群容错策略 正常情况下,当我们进行系统设计时候,不仅要考虑正常逻辑下代码该如何走,还要考虑异常情况下代码逻辑应该怎么走.当服务消费方调用服务提供方的服务出现错误时候,Dubbo提供了多种 ...
- html5 canvas 对角线渐变
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- [转载]TypeScript 入门指南
之前有听过,但未使用过,而最近在用nodejs,angularjs做一些前端项目,想到了这个来,正是学习TypeScript的时候,看介绍貌似和coffeescript相似,也JavaScript的转 ...