20231025
平衡树
前期的内容写在PPT上面。。。
平衡树线段树五问:
- 每个节点需要记录那些信息?
- 需要哪些标记?
- 如何下传标记?
- 如何区间整体修改?
- 如何合并区间(上传信息)?
P3215 [HNOI2011] 括号修复 / [JSOI2011]括号序列
Statement
现在给你一个长度为 \(n\) 的由(
和)
组成的字符串,位置标号从 \(1\) 到 \(n\)。对这个字符串有下列四种操作:
-
Replace a b c
:将 \([a,b]\) 之间的所有括号改成 \(c\)。假设原来的字符串为:))())())(
,那么执行操作Replace 2 7 (
后原来的字符串变为:)(((((()(
。 -
Swap a b
:将 \([a,b]\) 之间的字符串翻转。假设原来的字符串为:))())())(
,那么执行操作Swap 3 5
后原来的字符串变为:))))(())(
。 -
Invert a b
:将 \([a,b]\) 之间的(
变成)
,)
变成(
。假设原来的字符串为:))())())(
,那么执行操作Invert 4 8
后原来的字符串变为:))((()(((
。 -
Query a b
:询问 \([a,b]\) 之间的字符串至少要改变多少位才能变成合法的括号序列。改变某位是指将该位的(
变成)
或)
变成(
。注意执行操作Query
并不改变当前的括号序列。假设原来的字符串为:))())())(
,那么执行操作Query 3 6
的结果为 \(2\),因为要将位置 \(5\) 的)
变成(
并将位置 \(6\) 的(
变成)
。
对于 \(100\%\) 的数据,\(1\le n,q \le 10^5\)。
Solution
问题主要在于 query
时的查询,
我们钦定 (
记作 -1
,)
记作 1
,于是我们只需要维护前缀最大值 \(prmx\) 和后缀最小值 \(sfmn\) ,那么一个区间的答案就是:
画画图就很容易证明。
于是现在考虑具体操作:
- 每个节点需要记录那些信息:区间和,前缀最大最小,后缀最大最小
- 需要哪些标记:翻转标记,覆盖标记,取反标记
- 如何下传标记:翻转(直接像文艺平衡树),覆盖(把区间维护的信息都改变),取反(前后缀最大最小交换,\(prmx\to-prmn,prmn\to -prmax\))
- 区间整体修改?把前驱后继分别转到根和根的右儿子。
- 合并区间?枚举是否有左右儿子即可。
为什么有那么长的代码???
注意前面有一个哨兵,所以转的时候是转 \(l,r+2\)
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,m,cnt=0,rt=0,a[N];
struct Splay{
int fa,siz,sum,val,prmx,prmn,sfmx,sfmn,s[2],inv,rev,cov;
}tr[N];
#define lc(p) tr[p].s[0]
#define rc(p) tr[p].s[1]
void pu(int p){
tr[p].siz=tr[lc(p)].siz+tr[rc(p)].siz+1;
tr[p].sum=tr[lc(p)].sum+tr[rc(p)].sum+tr[p].val;
tr[p].prmx=max(tr[lc(p)].prmx,tr[rc(p)].prmx+tr[lc(p)].sum+tr[p].val);
tr[p].prmn=min(tr[lc(p)].prmn,tr[rc(p)].prmn+tr[lc(p)].sum+tr[p].val);
tr[p].sfmx=max(tr[rc(p)].sfmx,tr[rc(p)].sum+tr[p].val+tr[lc(p)].sfmx);
tr[p].sfmn=min(tr[rc(p)].sfmn,tr[rc(p)].sum+tr[p].val+tr[lc(p)].sfmn);
}
void cov(int p,int val){
if(!p) return;
tr[p].cov=val;
if(val==1){
tr[p].sum=tr[p].siz;
tr[p].prmn=tr[p].sfmn=0;
tr[p].prmx=tr[p].sfmx=tr[p].sum;
}
else{
tr[p].sum=~tr[p].siz+1;
tr[p].prmx=tr[p].sfmx=0;
tr[p].prmn=tr[p].sfmn=tr[p].sum;
}
tr[p].val=val;
tr[p].inv=0;
}
void rev(int p){
if(!p) return;
tr[p].rev^=1;
swap(lc(p),rc(p));
swap(tr[p].prmx,tr[p].sfmx);
swap(tr[p].prmn,tr[p].sfmn);
}
void inv(int p){
if(!p) return;
if(tr[p].cov!=0){
if(tr[p].cov==1) cov(p,-1);
else cov(p,1);
return;
}
tr[p].inv^=1;
int t=tr[p].prmx;
tr[p].prmx=~tr[p].prmn+1;
tr[p].prmn=~t+1;
t=tr[p].sfmx;
tr[p].sfmx=~tr[p].sfmn+1;
tr[p].sfmn=~t+1;
tr[p].sum=~tr[p].sum+1;
tr[p].val=~tr[p].val+1;
}
void pd(int p){
if(tr[p].cov!=0){
cov(lc(p),tr[p].cov);
cov(rc(p),tr[p].cov);
tr[p].cov=0;
}
if(tr[p].inv!=0) inv(lc(p)),inv(rc(p)),tr[p].inv=0;
if(tr[p].rev!=0) rev(lc(p)),rev(rc(p)),tr[p].rev=0;
}
void rotate(int x){
int y=tr[x].fa,z=tr[y].fa;
int k=(rc(y)==x);
pd(y);pd(x);
tr[y].s[k]=tr[x].s[k^1];
tr[tr[x].s[k^1]].fa=y;
tr[x].s[k^1]=y;
tr[y].fa=x;
tr[z].s[(rc(z)==y)]=x;
tr[x].fa=z;
pu(y);pu(x);
}
void splay(int x,int k){
while(tr[x].fa!=k){
int y=tr[x].fa,z=tr[y].fa;
if(z!=k) ((rc(y)==x)^(rc(z)==y))?rotate(x):rotate(y);
rotate(x);
}
if(k==0) rt=x;
}
int find(int k){
int p=rt;
while(k){
pd(p);
if(k<=tr[lc(p)].siz) p=lc(p);
else if(k>tr[lc(p)].siz+1) k-=tr[lc(p)].siz+1,p=rc(p);
else break;
}
splay(p,0);
return p;
}
void invert(int l,int r){
int x=find(l),y=find(r+2);
splay(x,0);splay(y,x);
inv(lc(y));
pu(y);pu(x);
}
void reverse(int l,int r){
int x=find(l),y=find(r+2);
splay(x,0);splay(y,x);
rev(lc(y));
pu(y);pu(x);
}
void replace(int l,int r,int val){
int x=find(l),y=find(r+2);
splay(x,0);splay(y,x);
cov(lc(y),val);
pu(y);pu(x);
}
int query(int l,int r){
int x=find(l),y=find(r+2);
splay(x,0);splay(y,x);
x=lc(y);
return ((tr[x].prmx+1)/2-(tr[x].sfmn-1)/2);
}
#define mid ((l+r)>>1)
int build(int l,int r,int fa){
if(l>r) return 0;
int p=++cnt;
tr[p].val=tr[p].sum=a[mid];
tr[p].siz=1;tr[p].fa=fa;
tr[p].s[0]=build(l,mid-1,p);
tr[p].s[1]=build(mid+1,r,p);
pu(p);return p;
}
void dfs(int p){
if(!p) return;
pd(p);
dfs(lc(p));
printf("%d:%d %d->%d %d\n",p,lc(p),rc(p),tr[p].val,tr[p].siz);
printf("%d %d %d %d %d\n",tr[p].sum,tr[p].prmx,tr[p].prmn,tr[p].sfmx,tr[p].sfmn);
dfs(rc(p));
}
int main(){
/*2023.10.23 H_W_Y P3215 [HNOI2011] 括号修复 / [JSOI2011]括号序列 Splay*/
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
char s=getchar();
while(s!='('&&s!=')') s=getchar();
if(s=='(') a[i]=-1;
else a[i]=1;
}
rt=build(0,n+1,0);
for(int i=1;i<=m;i++){
char s[15];int l,r;
scanf("%s",s);
scanf("%d%d",&l,&r);
if(s[0]=='R'){
char ch;ch=getchar();
while(ch!='('&&ch!=')') ch=getchar();
replace(l,r,(ch=='(')?-1:1);
}
if(s[0]=='I') invert(l,r);
if(s[0]=='S') reverse(l,r);
if(s[0]=='Q') printf("%d\n",query(l,r));
}
return 0;
}
P4036 [JSOI2008] 火星人
Statement
序号 1 2 3 4 5 6 7 8 9 10 11
字符 m a d a m i m a d a m
现在,火星人定义了一个函数 \(LCQ(x, y)\),表示:该字符串中第 \(x\) 个字符开始的字串,与该字符串中第 \(y\) 个字符开始的字串,两个字串的公共前缀的长度。比方说,\(LCQ(1, 7) = 5, LCQ(2, 10) = 1, LCQ(4, 7) = 0\)
在求取 \(LCQ\) 函数的同时,还可以改变字符串本身。具体地说,可以更改字符串中某一个字符的值,也可以在字符串中的某一个位置插入一个字符。
- 所有字符串自始至终都只有小写字母构成。
- \(M\leq150,000\)
- 字符串长度L自始至终都满足\(L\leq100,000\)
- 询问操作的个数不超过 \(10,000\) 个。
Solution
直接平衡树维护哈希值,查询时二分即可。
注意数据范围!!!
#include <bits/stdc++.h>
using namespace std;
#define ll unsigned long long
mt19937 rd(time(0));
const int N=5e5+5;
int n,l,r,x,y,idx=0,rt=0,z,pos;
const ll base=27;
ll hs[N];
char s[N];
struct Treap{
int val,key,siz,s[2];
ll hs;
}tr[N];
void init(){
hs[0]=1ll;
for(int i=1;i<=100000;i++) hs[i]=hs[i-1]*base;
}
int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
void print(int x){
int p[15],tmp=0;
if(x==0) putchar('0');
if(x<0) putchar('-'),x=-x;
while(x){
p[++tmp]=x%10;
x/=10;
}
for(int i=tmp;i>=1;i--) putchar(p[i]+'0');
putchar('\n');
}
#define lc(p) tr[p].s[0]
#define rc(p) tr[p].s[1]
int nwnode(int v){
idx++;
tr[idx].val=tr[idx].hs=v;
tr[idx].key=rd();
tr[idx].siz=1;
tr[idx].s[0]=tr[idx].s[1]=0;
return idx;
}
void pu(int p){
tr[p].siz=tr[lc(p)].siz+tr[rc(p)].siz+1;
tr[p].hs=tr[lc(p)].hs+1ll*tr[p].val*hs[tr[lc(p)].siz]+tr[rc(p)].hs*hs[tr[lc(p)].siz+1];
}
void split(int p,int k,int &x,int &y){
if(!p){x=y=0;return;}
if(tr[lc(p)].siz<k) x=p,split(rc(p),k-tr[lc(p)].siz-1,rc(x),y);
else y=p,split(lc(p),k,x,lc(p));
pu(p);
}
int merge(int x,int y){
if(!x||!y) return (x|y);
if(tr[x].key<tr[y].key){
tr[x].s[1]=merge(tr[x].s[1],y);
pu(x);return x;
}
else{
tr[y].s[0]=merge(x,tr[y].s[0]);
pu(y);return y;
}
}
void ins(int k,int val){
split(rt,k,x,y);
rt=merge(merge(x,nwnode(val)),y);
}
void del(int k){
split(rt,k,x,z);
split(x,k-1,x,y);
y=merge(lc(y),rc(y));
rt=merge(merge(x,y),z);
}
ll query(int l,int r){
split(rt,r,x,z);
split(x,l-1,x,y);
ll res=tr[y].hs;
rt=merge(merge(x,y),z);
return res;
}
int main(){
/*2023.10.24 H_W_Y P4036 [JSOI2008] 火星人 FHQ-Treap*/
scanf("%s",s);init();
for(int i=0;i<(int)strlen(s);i++) ins(i,s[i]-'a');
n=read();
for(int i=1;i<=n;i++){
scanf("%s",s);
if(s[0]=='Q'){
int L=read(),R=read();
l=0,r=tr[rt].siz-max(L,R)+1;
while(l<r){
int mid=(l+r+1)/2;
if(query(L,L+mid-1)==query(R,R+mid-1)) l=mid;
else r=mid-1;
}
print(l);
}
else if(s[0]=='R'){
pos=read();char ch=getchar();
del(pos);ins(pos-1,ch-'a');
}
else{
pos=read();char ch=getchar();
ins(pos,ch-'a');
}
}
return 0;
}
P4200 千山鸟飞绝
最后一道题哩,写起来怪激动的。
Statement
给定二维平面上的 \(n\) 个带权点,每一时刻改变一个点的坐标,最后问对于每一个点,在每一时刻内与其坐标相同的其他点的个数的最大值 \(a\) 和权值最大值的最大值 \(b\) 之积。
\(1 \le n \le 30000,0 \le m \le 300000\)
Solution
首先需要将坐标离散化,再用多棵平衡树维护。
发现删除一个点的答案一定不会更有,而在加入一个点的时候,
我们首先可以先把该坐标上的点打上标记,再对当前点的答案更新,
直接加入即可。
具体而言,对于每一个节点我们维护 \(mx,siz,tag1,tag2\),
每次加入一个节点时,我们用 \(mx\) 来更新新加入节点的答案,
而本来的平衡树中 \(tag1=\max(tag1,val),tag2=\max(tag2,siz)\) 即可。
居然一过了!!!
#include <bits/stdc++.h>
using namespace std;
#define ll long long
mt19937 rd(time(0));
const int N=3e5+5;
const ll inf=2e9;
int n,m,rt[N],idx=0,w[N],x,y,z;
ll pos[N],ans1[N],ans2[N];
struct node{
ll w,x,y,pos;
}a[N],b[N];
struct Treap{
int siz,id,s[2],key;
ll val,tag1,tag2,mx,se;
}tr[N];
queue<int> q;
int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
void print(ll x){
int p[25],tmp=0;
if(x==0) putchar('0');
if(x<0) putchar('-'),x=-x;
while(x){
p[++tmp]=x%10;
x/=10;
}
for(int i=tmp;i>=1;i--) putchar(p[i]+'0');
putchar('\n');
}
#define lc(p) tr[p].s[0]
#define rc(p) tr[p].s[1]
void pu(int p){
tr[p].siz=tr[lc(p)].siz+tr[rc(p)].siz+1;
tr[p].mx=max(tr[p].val,max(tr[lc(p)].mx,tr[rc(p)].mx));
if(tr[p].mx==tr[p].val) tr[p].se=max(tr[lc(p)].mx,tr[rc(p)].mx);
else if(tr[p].mx==tr[lc(p)].mx) tr[p].se=max(max(tr[p].val,tr[lc(p)].se),tr[rc(p)].mx);
else tr[p].se=max(max(tr[p].val,tr[rc(p)].se),tr[lc(p)].mx);
}
void change(int p,ll tag1,ll tag2){
if(!p) return;
int id=tr[p].id;
ans1[id]=max(ans1[id],tag1);
ans2[id]=max(ans2[id],tag2);
tr[p].tag1=max(tr[p].tag1,tag1);
tr[p].tag2=max(tr[p].tag2,tag2);
}
void pd(int p){
if(!p) return ;
if(lc(p)) change(lc(p),tr[p].tag1,tr[p].tag2);
if(rc(p)) change(rc(p),tr[p].tag1,tr[p].tag2);
tr[p].tag1=tr[p].tag2=0;
}
int nwnode(ll val,int id){
int it=0;
if(!q.empty()) it=q.front(),q.pop();
else it=++idx;
tr[it].id=id;
tr[it].key=rd();
tr[it].mx=tr[it].val=val;
tr[it].siz=1;
tr[it].se=-inf;
tr[it].s[0]=tr[it].s[1]=tr[it].tag1=tr[it].tag2=0;
return it;
}
void split(int p,int k,int &x,int &y){
if(!p){x=y=0;return;}
pd(p);
if(tr[p].id<=k) x=p,split(rc(p),k,rc(p),y);
else y=p,split(lc(p),k,x,lc(p));
pu(p);
}
int merge(int x,int y){
if(!x||!y) return (x|y);
pd(x);pd(y);
if(tr[x].key<tr[y].key){
tr[x].s[1]=merge(tr[x].s[1],y);
pu(x);return x;
}
else {
tr[y].s[0]=merge(x,tr[y].s[0]);
pu(y);return y;
}
}
void del(int id){
int &rot=rt[w[id]];
split(rot,id,x,z);
split(x,id-1,x,y);
q.push(y);
y=merge(lc(y),rc(y));
rot=merge(merge(x,y),z);
}
void ins(int id,int p){
int &rot=rt[p];
ans1[id]=max(ans1[id],tr[rot].mx);
ans2[id]=max(ans2[id],1ll*tr[rot].siz);
change(rot,a[id].w,tr[rot].siz);
split(rot,id,x,y);
rot=merge(merge(x,nwnode(a[id].w,id)),y);
w[id]=p;
}
void dfs(int p){
if(!p) return;
pd(p);
if(lc(p)) dfs(lc(p));
if(rc(p)) dfs(rc(p));
}
int main(){
/*2023.10.25 H_W_Y P4200 千山鸟飞绝 FHQ-Treap*/
n=read();
for(int i=1;i<=n;i++){
a[i].w=1ll*read(),a[i].x=1ll*read(),a[i].y=1ll*read();
pos[i]=a[i].pos=a[i].x*inf+a[i].y;
}
m=read();
for(int i=1;i<=m;i++){
b[i].w=read(),b[i].x=1ll*read(),b[i].y=1ll*read();
pos[i+n]=b[i].pos=b[i].x*inf+b[i].y;
}
sort(pos+1,pos+n+m+1);
int len=unique(pos+1,pos+n+m+1)-pos-1;
for(int i=1;i<=n;i++) a[i].pos=lower_bound(pos+1,pos+len+1,a[i].pos)-pos;
for(int i=1;i<=m;i++) b[i].pos=lower_bound(pos+1,pos+len+1,b[i].pos)-pos;
for(int i=1;i<=n;i++) ins(i,a[i].pos);
for(int i=1;i<=m;i++) del(b[i].w),ins(b[i].w,b[i].pos);
for(int i=1;i<=len;i++) dfs(rt[i]);
for(int i=1;i<=n;i++) print(ans1[i]*ans2[i]);
return 0;
}
CF702F T-Shirts
Statement
有 \(n\) 种 T 恤,每种有价格 \(c_i\) 和品质 \(q_i\)。
有 \(m\) 个人要买 T 恤,第 \(i\) 个人有 \(v_i\) 元,每人每次都会买一件能买得起的 \(q_i\) 最大的 T 恤。一个人只能买一种 T 恤一件,所有人之间都是独立的。
问最后每个人买了多少件 T 恤?如果有多个 \(q_i\) 最大的 T 恤,会从价格低的开始买。
\(1 \le n,m \le 2 \times 10^5\) ,时限 \(4s\)
Solution
首先不难想到对于 \(n\) 种 T 恤,我们按照 \(q_i\) 从大到小排序,
如果 \(q_i\) 相同则按照 \(c_i\) 从小到大排序。
对于每一个人,我们可以从前往后遍历一遍,能买就买了。
现在考虑是否可以对于每一种 T恤,我们对整个序列进行操作,
具体来说也就是把 \(\ge c_i\) 的数全部减去 \(c_i\),并且 \(ans+1\) 。
发现这样并不能保证序列的单调性,
中间在 \(c_i \le val \lt 2 c_i\) 的数有可能产生冲突,
那怎么办呢?
由于 \(c_i \le val \lt 2c_i\) ,那么 \(c_i \gt \frac{val}{2}\),
所以对于这样的 \(val\) ,我们直接进行暴力的删除和插入,
因为这样的操作最多进行 \(\log val\) 次,每次都会减小到原来的一半。
时限是 \(4s\) ,足以通过,用平衡树维护即可。
时间复杂度 \(O((n+\sum \log_2q_i)\log_2 n)\)。
注意标记要清空!!!
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,idx=0,rt,m,ans[N],val[N],x,y,z;
struct node{
int c,q;
bool operator <(const node &rhs) const{
if(q!=rhs.q) return q>rhs.q;
return c<rhs.c;
}
}a[N];
struct Treap{
int siz,val,id,s[2],key,tag,add;
}tr[N];
mt19937 rd(time(0));
queue<int> q;
int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
void print(int x){
int p[15],tmp=0;
if(x==0) putchar('0');
if(x<0) putchar('-'),x=-x;
while(x){
p[++tmp]=x%10;
x/=10;
}
for(int i=tmp;i>=1;i--) putchar(p[i]+'0');
putchar(' ');
}
#define lc(p) tr[p].s[0]
#define rc(p) tr[p].s[1]
int nwnode(int val,int id){
int it;
if(!q.empty()) it=q.front(),q.pop();
else it=++idx;
tr[it].val=val;
tr[it].id=id;
tr[it].key=rd();
tr[it].siz=1;
tr[it].s[0]=tr[it].s[1]=tr[it].tag=tr[it].add=0;
return it;
}
void pu(int p){tr[p].siz=tr[lc(p)].siz+tr[rc(p)].siz+1;}
void change(int p,int tag,int add){
if(!p) return ;
tr[p].tag+=tag;
tr[p].add+=add;
ans[tr[p].id]+=add;
tr[p].val+=tag;
}
void pd(int p){
if(tr[p].add==0) return;
if(lc(p)) change(lc(p),tr[p].tag,tr[p].add);
if(rc(p)) change(rc(p),tr[p].tag,tr[p].add);
tr[p].tag=0;tr[p].add=0;
}
void split(int p,int k,int &x,int &y){
if(!p){x=y=0;return;}
pd(p);
if(tr[p].val<=k) x=p,split(rc(p),k,rc(p),y);
else y=p,split(lc(p),k,x,lc(p));
pu(p);
}
int merge(int x,int y){
if(!x||!y) return (x|y);
pd(x);pd(y);
if(tr[x].key<tr[y].key){
tr[x].s[1]=merge(tr[x].s[1],y);
pu(x);return x;
}
else{
tr[y].s[0]=merge(x,tr[y].s[0]);
pu(y);return y;
}
}
void ins(int &p,int val,int id){
int xx=0,yy=0;
split(p,val,xx,yy);
p=merge(merge(xx,nwnode(val,id)),yy);
}
void dfs(int p,int c){
if(!p) return ;
pd(p);
if(lc(p)) dfs(lc(p),c);
if(rc(p)) dfs(rc(p),c);
q.push(p);ans[tr[p].id]++;
if(tr[p].val-c>0) ins(x,tr[p].val-c,tr[p].id);
}
void update(int c){
split(rt,c-1,x,y);
split(y,2*c,y,z);
if(z) change(z,-c,1);
dfs(y,c);
rt=merge(x,z);
}
void getans(int p){
pd(p);
if(lc(p)) getans(lc(p));
if(rc(p)) getans(rc(p));
}
int main(){
/*2023.10.25 H_W_Y CF702F T-Shirts FHQ-Treap*/
n=read();
for(int i=1;i<=n;i++) a[i].c=read(),a[i].q=read();
sort(a+1,a+n+1);
m=read();
for(int i=1;i<=m;i++) val[i]=read(),ins(rt,val[i],i);
for(int i=1;i<=n;i++) update(a[i].c);
getans(rt);
for(int i=1;i<=m;i++) print(ans[i]);
return 0;
}
CF809D Hitchhiking in the Baltic States
Statement
给出 \(n\) 个区间 \([l_i,r_i]\) 和 \(n\) 个未知数 \(a_1,a_2,\dots,a_n\),现在你要确定这 \(n\) 个数,使得 \(a_i\in[l_i,r_i]\),并且这个序列的最长严格上升子序列尽可能大,求这个最大值。
\(1 \leq n\leq 3\times 10^5\),\(1 \leq l_i,r_i\leq 10^9\)。
Solution
好题!
P3835 【模板】可持久化平衡树
Statement
您需要写一种数据结构(可参考题目标题),来维护一个可重整数集合,其中需要提供以下操作( 对于各个以往的历史版本 ):
1、 插入 \(x\)
2、 删除 \(x\)(若有多个相同的数,应只删除一个,如果没有请忽略该操作)
3、 查询 \(x\) 的排名(排名定义为比当前数小的数的个数 \(+1\))
4、查询排名为 \(x\) 的数
5、 求 \(x\) 的前驱(前驱定义为小于 \(x\),且最大的数,如不存在输出 \(-2^{31}+1\) )
6、求 \(x\) 的后继(后继定义为大于 \(x\),且最小的数,如不存在输出 \(2^{31}-1\) )
对于 \(100\%\) 的数据, $ 1 \leq n \leq 5 \times 10^5 $ , \(|x_i| \leq {10}^9\),\(0 \le v_i < i\),\(1\le \text{opt} \le 6\)。
Solution
顾名思义,就是可持久化的平衡树。
一般使用 FHQ -Treap完成,和普通平衡树的板子没有什么太大区别,
就是在 Split 的时候加上了新建一个节点的操作,再维护一下每一个状态的根即可。
#include <bits/stdc++.h>
using namespace std;
mt19937 rd(time(0));
#define ll long long
const int N=5e5+5;
int n,m,idx=0,rt[N];
struct Treap{
int s[2],siz,key;
ll val;
}tr[N*50];
int nwnode(ll v=0){
tr[++idx].val=v;
tr[idx].key=rd();
tr[idx].siz=1;
tr[idx].s[0]=tr[idx].s[1]=0;
return idx;
}
#define lc(p) tr[p].s[0]
#define rc(p) tr[p].s[1]
void pushup(int p){tr[p].siz=tr[lc(p)].siz+tr[rc(p)].siz+1;}
int merge(int x,int y){
if(!x||!y) return (x|y);
if(tr[x].key<tr[y].key){
tr[x].s[1]=merge(tr[x].s[1],y);
pushup(x);return x;
}
else{
tr[y].s[0]=merge(x,tr[y].s[0]);
pushup(y);return y;
}
}
void split(int p,ll k,int &x,int &y){
if(!p){x=y=0;return;}
if(tr[p].val<=k){
x=nwnode();
tr[x]=tr[p];
split(tr[x].s[1],k,tr[x].s[1],y);
pushup(x);
}
else{
y=nwnode();
tr[y]=tr[p];
split(tr[y].s[0],k,x,tr[y].s[0]);
pushup(y);
}
}
int find(int p,int k){
while(1){
if(k<=tr[lc(p)].siz) p=lc(p);
else{
if(k>tr[lc(p)].siz+1) k-=tr[lc(p)].siz+1,p=rc(p);
else return p;
}
}
}
int main(){
/*2023.10.23 H_W_Y P3835 【模板】可持久化平衡树 FHQ-Treap*/
scanf("%d",&n);
for(int i=1;i<=n;i++){
int x=0,y=0,z=0,op,tmp;ll val;
scanf("%d%d%lld",&tmp,&op,&val);
rt[i]=rt[tmp];
if(op==1){
split(rt[i],val,x,y);
rt[i]=merge(merge(x,nwnode(val)),y);
}
if(op==2){
split(rt[i],val,x,z);
split(x,val-1,x,y);
y=merge(tr[y].s[0],tr[y].s[1]);
rt[i]=merge(merge(x,y),z);
}
if(op==3){
split(rt[i],val-1,x,y);
printf("%d\n",tr[x].siz+1);
rt[i]=merge(x,y);
}
if(op==4){
printf("%lld\n",tr[find(rt[i],val)].val);
}
if(op==5){
split(rt[i],val-1,x,y);
if(x==0) printf("-2147483647\n");
else printf("%lld\n",tr[find(x,tr[x].siz)].val),rt[i]=merge(x,y);
}
if(op==6){
split(rt[i],val,x,y);
if(y==0) printf("2147483647\n");
else printf("%lld\n",tr[find(y,1)].val),rt[i]=merge(x,y);
}
}
return 0;
}
P5055 【模板】可持久化文艺平衡树
Statement
您需要写一种数据结构,来维护一个序列,其中需要提供以下操作(对于各个以往的历史版本):
- 在第 \(p\) 个数后插入数 \(x\)。
- 删除第 \(p\) 个数。
- 翻转区间 \([l,r]\),例如原序列是 \(\{5,4,3,2,1\}\),翻转区间 \([2,4]\) 后,结果是 \(\{5,2,3,4,1\}\)。
- 查询区间 \([l,r]\) 中所有数的和。
和原本平衡树不同的一点是,每一次的任何操作都是基于某一个历史版本,同时生成一个新的版本(操作 \(4\) 即保持原版本无变化),新版本即编号为此次操作的序号。
本题强制在线。
对于 \(100\%\) 的数据,\(1 \le n \le 2 \times {10}^5\),\(|x_i| < {10}^6\)。
Solution
同样是在每一次 split 的时候每次复制一个节点。
注意 \(lstans\) 是 long long,所以 \(l,r\) 都要开 long long。
#include <bits/stdc++.h>
using namespace std;
mt19937 rd(time(0));
#define ll long long
const int N=2e5+5;
int n,idx=0,rt[N];
ll lstans=0;
struct Treap{
int key,s[2],siz,tag;
ll val,sum;
}tr[N<<7];
int nwnode(ll v=0){
idx++;
tr[idx].val=tr[idx].sum=v;
tr[idx].key=rd();
tr[idx].siz=1;
tr[idx].s[0]=tr[idx].s[1]=tr[idx].tag=0;
return idx;
}
#define lc(p) tr[p].s[0]
#define rc(p) tr[p].s[1]
void pu(int p){
tr[p].siz=tr[lc(p)].siz+tr[rc(p)].siz+1;
tr[p].sum=tr[lc(p)].sum+tr[rc(p)].sum+tr[p].val;
}
int cn(int p){
int x=nwnode();
tr[x]=tr[p];
return x;
}
void pd(int p){
if(!tr[p].tag) return ;
if(lc(p)) lc(p)=cn(lc(p));
if(rc(p)) rc(p)=cn(rc(p));
swap(lc(p),rc(p));
if(lc(p)) tr[lc(p)].tag^=1;
if(rc(p)) tr[rc(p)].tag^=1;
tr[p].tag=0;
}
void split(int p,int k,int &x,int &y){
if(!p){x=y=0;return;}
pd(p);
if(tr[lc(p)].siz<k){
x=cn(p);
split(rc(x),k-tr[lc(x)].siz-1,rc(x),y);
pu(x);
}
else {
y=cn(p);
split(lc(y),k,x,lc(y));
pu(y);
}
}
int merge(int x,int y){
if(!x||!y) return (x|y);
pd(x);pd(y);
if(tr[x].key<tr[y].key){
tr[x].s[1]=merge(tr[x].s[1],y);
pu(x);return x;
}
else{
tr[y].s[0]=merge(x,tr[y].s[0]);
pu(y);return y;
}
}
int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
int main(){
/*2023.10.24 H_W_Y P5055 【模板】可持久化文艺平衡树 FHQ-Treap*/
n=read();
for(int i=1;i<=n;i++){
int v,opt;ll val,l,r;
int x=0,y=0,z=0;
scanf("%d%d",&v,&opt);
rt[i]=rt[v];
if(opt==1){
scanf("%lld%lld",&l,&val);
l^=lstans;val^=lstans;
split(rt[v],l,x,y);
rt[i]=merge(x,merge(nwnode(val),y));
}
if(opt==2){
scanf("%lld",&l);l^=lstans;
split(rt[v],l,x,z);
split(x,l-1,x,y);
rt[i]=merge(x,z);
}
if(opt==3){
scanf("%lld%lld",&l,&r);
l^=lstans;r^=lstans;
split(rt[i],r,x,z);
split(x,l-1,x,y);
tr[y].tag^=1;
rt[i]=merge(merge(x,y),z);
}
if(opt==4){
scanf("%lld%lld",&l,&r);
l^=lstans;r^=lstans;
split(rt[v],r,x,z);
split(x,l-1,x,y);
printf("%lld\n",lstans=tr[y].sum);
rt[i]=merge(merge(x,y),z);
}
}
return 0;
}
标签:lc,val,int,siz,tr,rc,平衡
From: https://www.cnblogs.com/hwy0622/p/balancetree.html