题解:P2617 Dynamic Rankings
P2617 解题报告
前言
大家好,我非常喜欢 STL,所以我用 vector 过了这道题。
思路分析
看题,发现题目要求维护序列上:
-
单点修改
-
查询区间第
k 大值。
不难发现这就是 P3380 【模板】树套树 的操作 2 和操作 3。
具体来说,可以维护一棵线段树套平衡树,在线段树的每一个节点维护一棵平衡树,平衡树内维护的数就是线段树节点所代表的区间上的数。
这样,两个操作可以这样做:
-
在线段树上递归区间,将递归到的节点的平衡树上删除原数值,添加新数值。
-
二分答案,问题转化为求一个数在区间内的排名。考虑将询问区间拆分成线段树上节点,在每个节点的平衡树上查询小于当前二分值的数的个数。
使用线段树套平衡树的复杂度为
但是在考场上写树套树,对于像我一样的普及组选手有点困难 (普及组选手做树套树题?),所以我们考虑降低实现难度。
有请今天的主角:vector!
我们考虑用 vector 替换掉内层的平衡树。
考虑使用 vector 中 insert 和 erase 成员函数,以及 stl 中的 lower_bound 函数,维护一个有序(具体来说是单调不降)的线性表。
这样,两个操作可以这样做:
-
外层线段树部分同上,对于内层 vector 的修改先用 lower_bound 函数找到
x 的位置,然后调用 insert/erase 函数。 -
二分和外层线段树部分同上,对于内层 vector 的查询在每个线段树节点上的 vector 上用 lower_bound 函数直接查询小于
x 的数的个数。
注意,vector 中 insert 和 erase 成员函数的复杂度都是
使用线段树套 vector 的复杂度已经达到了令人发指的 令人发指的常数,这样实现的时间甚至超越了某些常数巨大的线段树套 Splay。(这是 vector 的胜利!)
代码实现
都用 vector 了还不试着自己写写。
vector 替代平衡树部分(请忽略自欺欺人的变量名):
struct FHQ{
vector<int> v;
void insert(int x){
v.insert(lower_bound(v.begin(),v.end(),x),x);
}
void delate(int x){
if(!v.size() || x==-1) return;
v.erase(lower_bound(v.begin(),v.end(),x));
}
int get_rk(int x){
return lower_bound(v.begin(),v.end(),x)-v.begin()+1;
}
}treap[200005];
线段树部分:
struct segment_tree{
int val[200005],ls[200005],rs[200005],dcnt;
void build(int l,int r,int &x){
x=++dcnt;
if(l==r){
val[l]=-1;
return;
}
int mid=(l+r)>>1;
build(l,mid,ls[x]);
build(mid+1,r,rs[x]);
}
void modify(int l,int r,int pos,int k,int x){
treap[x].delate(val[pos]);
treap[x].insert(k);
if(l==r){
val[pos]=k;
return;
}
int mid=(l+r)>>1;
if(pos<=mid) modify(l,mid,pos,k,ls[x]);
else modify(mid+1,r,pos,k,rs[x]);
}
int query_rk(int l,int r,int ql,int qr,int k,int x){
if(ql<=l && r<=qr) return treap[x].get_rk(k)-1;
int mid=(l+r)>>1,ans=0;
if(ql<=mid) ans+=query_rk(l,mid,ql,qr,k,ls[x]);
if(qr>=mid+1) ans+=query_rk(mid+1,r,ql,qr,k,rs[x]);
return ans;
}
int query_k(int ql,int qr,int k){
int l=0,r=1e9,ans=0;
while(l<=r){
int mid=(l+r)>>1;
if(query_rk(1,n,ql,qr,mid,1)+1<=k){
ans=mid;
l=mid+1;
}else{
r=mid-1;
}
}
return ans;
}
}tree;
AC 记录(封装版,无卡常,码风良好)
封装的这么好,主函数就自己写吧。
补:关于 vector 替代平衡树的一些测试
临近 CSP/NOIP,大家刷题还是要注重实用性的。
关于是否能在考场上用 vector 来替代平衡树,并且做到不挂分,看网上说法不一。本着实践出真知的理念,这里为大家提供 vector 和现 OI 主流平衡树(Splay,FHQ-Treap,替罪羊树,pbds)的时间耗用对比测试。
代码
vector
Splay(数组实现)
FHQ-Treap(数组实现)
替罪羊树(指针实现)
pbds
评测方式/环境/语言
方式:LemonLime
数据:数据脚造,数据分为三组:
-
随机数据,n=100000,对标ds优化题数据强度;
-
随机数据,n=1000000,对标纯ds题数据强度;
-
构造数据,n不等,卡vector。
本机环境:Windows 11,Intel Core i5,x64,1.70 GHz (省流:很旧,跑的很慢)
语言:C++14 O2 (gcc 9.3.0)
评测结果
here.(提取码 lxt3)
分析总结
-
在随机数据下,vector 常数远小于其余所有平衡树;
-
在构造数据下,vector 被卡回
O(n^2) ; -
pbds 在数据范围增大时常数变大,不能通过全部极限数据。
所以,我的建议是,vector 模拟赛随便用,正赛慎重使用,当心一场空。
写在最后
祝各位读者 CSP 2024 / NOIP 2024 RP++ !