动态标号学习笔记
前置知识
重量平衡树。
P6272 [湖北省队互测2014] 没有人的算术
题意
定义一个集合 \(S=\{x|x=0 \lor x=(a,b),a,b\in S\}\)。
集合中元素大小关系的比较题目说的很清楚了。
要求维护一个序列 \(a\) 满足 \(a_i\in S\)。支持以下两种操作:
-
将 \(a_k\) 赋值为 \((a_l,a_r)\)。
-
查询区间 \([l,r]\) 的最大值位置。
思路
直接比较两个值显然会超时。但题目并没有要求维护每个位置的真实值,所以可以只维护两个值之间的大小关系。
接下来就是重点了。怎么维护?
我们可以采用动态标号的方式维护。
动态标号用来解决一类具体值不重要但是要快速比较大小关系的问题。
做法是,令每个数对应一个权值,在一棵重量平衡树上,动态维护每一个数的权值。
具体来说,每个权值是一个实数,用以快速比较数之间的大小关系。
实现
令根节点对应一个极大区间 \([0,\infty)\)。定义对应区间为 \([l,r)\) 的一个节点的权值为 \(\frac{l+r}{2}\),且满足左儿子对应区间为 \([l,\frac{l+r}{2})\),右儿子对应区间为 \([\frac{l+r}{2},r)\)。那么平衡树上任意两个节点的大小关系均可以 \(O(1)\) 的比较。
具体实现就是在平衡树上插入一个节点后暴力重构子树内的权值,根据重量平衡树的性质就可以做到期望或均摊单次 \(\mathcal{O}(\log n)\)。
于是每次赋值操作将 \(k\) 位置上的值记为 \((l,r)\),在平衡树上插入即可。然后开一颗线段树维护区间最值就可以了。
代码
#include <bits/stdc++.h>
using namespace std;
constexpr int N=1e5+5,M=6e5+5;
constexpr double eps=1e-9;
mt19937 rd(chrono::steady_clock::now().time_since_epoch().count());
double a[M]; // 记录值
pair<int,int>val[N];
vector<int>pos[M]; // 记录每个值都在哪些位置出现
struct FHQTreap{
int pri[M],ls[M],rs[M],size[M],rt,cnt;
pair<int,int>val[M];
int newnode(pair<int,int>x){
int p=++cnt;
ls[p]=rs[p]=0;
size[p]=1;
pri[p]=rd();
val[p]=x;
return p;
}
void pushup(int p){size[p]=size[ls[p]]+1+size[rs[p]];}
void split_rank(int p,int rank,int &rtl,int &rtr){
if(!p){rtl=rtr=0;return;}
if(rank<=size[ls[p]]){
rtr=p;
split_rank(ls[rtr],rank,rtl,ls[rtr]);
pushup(rtr);
}
else{
rtl=p;
split_rank(rs[rtl],rank-size[ls[rtl]]-1,rs[rtl],rtr);
pushup(rtl);
}
}
int merge(int rtl,int rtr){
if(!rtl||!rtr)return rtl+rtr;
if(pri[rtl]<pri[rtr]){
rs[rtl]=merge(rs[rtl],rtr);
pushup(rtl);
return rtl;
}
else{
ls[rtr]=merge(rtl,ls[rtr]);
pushup(rtr);
return rtr;
}
}
void get_val(int p,double l,double r){ // 重构子树
if(!p)return;
a[p]=(l+r)/2;
get_val(ls[p],l,(l+r)/2);
get_val(rs[p],(l+r)/2,r);
}
int get_rank(int p,pair<int,int>x){
int rank=0;
while(p){
if(x.first==val[p].first){
if(a[val[p].second]-a[x.second]>-eps)p=ls[p];
else rank+=size[ls[p]]+1,p=rs[p];
}
else if(a[val[p].first]-a[x.first]>eps)p=ls[p];
else rank+=size[ls[p]]+1,p=rs[p];
}
return rank;
}
pair<int,int> insert(int &p,int n,int x,double l=0,double r=1e9){
if(!p||pri[n]<pri[p]){
split_rank(p,get_rank(p,val[n]),ls[n],rs[n]);
get_val(p=n,l,r);
pushup(p);
pos[p].push_back(x);
return make_pair(p,pos[p].size()-1);
}
pair<int,int>res;
if(a[val[p].first]-a[val[n].first]>eps||(val[n].first==val[p].first&&a[val[p].second]-a[val[n].second]>eps))res=insert(ls[p],n,x,l,(l+r)/2);
else res=insert(rs[p],n,x,(l+r)/2,r);
pushup(p);
return res;
}
pair<int,int> search(pair<int,int>n,int x){
int p=rt;
while(p){
if(n.first==val[p].first&&n.second==val[p].second){
pos[p].push_back(x);
return make_pair(p,pos[p].size()-1);
}
if(a[n.first]<a[val[p].first]||(n.first==val[p].first&&a[n.second]<a[val[p].second]))p=ls[p];
else p=rs[p];
}
return make_pair(-1,-1);
}
pair<int,int> insert(pair<int,int>n,int x){
pair<int,int>res=search(n,x);
if(~res.first)return res;
return insert(rt,newnode(n),x);
}
}tr;
struct SegmentTree{
int maxn[N<<2],id[N<<2]; // 记录值对应的a数组下标和位置对应的pos数组下标
#define ls(p) (p<<1)
#define rs(p) (p<<1)|1
void pushup(int p){
if(a[maxn[ls(p)]]-a[maxn[rs(p)]]>eps||(fabs(a[maxn[ls(p)]]-a[maxn[rs(p)]])<eps&&pos[maxn[ls(p)]][id[ls(p)]]<pos[maxn[rs(p)]][id[rs(p)]]))maxn[p]=maxn[ls(p)],id[p]=id[ls(p)];
else maxn[p]=maxn[rs(p)],id[p]=id[rs(p)];
}
void update(int p,int pos,pair<int,int>val,int pl,int pr){
if(pl==pr){maxn[p]=val.first,id[p]=val.second;return;}
const int mid=(pl+pr)>>1;
if(mid>=pos)update(ls(p),pos,val,pl,mid);
else update(rs(p),pos,val,mid+1,pr);
pushup(p);
}
pair<int,int> query(int p,int l,int r,int pl,int pr){
if(l<=pl&&pr<=r)return make_pair(maxn[p],id[p]);
const int mid=(pl+pr)>>1;
if(mid>=r)return query(ls(p),l,r,pl,mid);
if(mid<l)return query(rs(p),l,r,mid+1,pr);
pair<int,int>resl=query(ls(p),l,r,pl,mid),resr=query(rs(p),l,r,mid+1,pr);
return a[resl.first]==a[resr.first]?(pos[resl.first][resl.second]<pos[resr.first][resr.second]?resl:resr):(a[resl.first]>a[resr.first]?resl:resr);
}
}t;
int n,m;
signed main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>m;
pos[0].reserve(n);
for(int i=0;i<n;i++)pos[0][i]=i+1;
for(int i=1;i<=n;i++)t.update(1,i,make_pair(0,i-1),1,n);
for(int i=1;i<=m;i++){
char op;cin>>op;
int l,r,x;cin>>l>>r;
if(op=='C'){
cin>>x;
val[x]=tr.insert(make_pair(val[l].first,val[r].first),x);
t.update(1,x,val[x],1,n);
}
else{
pair<int,int>ans=t.query(1,l,r,1,n);
cout<<pos[ans.first][ans.second]<<'\n';
}
}
return 0;
}
例题
动态标号的经典应用是后缀平衡树。这里不详细展开了。