树据结构1
可持久化线段树
普通可持久化线段树
又称主席树。
单点修改线段树时,我们只会改动 \(\log n\) 个节点,因此对于这 \(\log n\) 个节点新建位置,就可以新建一个时间点的状态。
void update(int &p,int lp,int l,int r,int x,int y){
a[p=++tot]=a[lp];
if(l==r)return a[p].w+=y,void();
if(x<=mid)update(a[p].ls,a[lp].ls,l,mid,x,y);
else update(a[p].rs,a[lp].rs,mid+1,r,x,y);
a[p].w=a[a[p].ls].w+a[a[p].rs].w;
}
主席树区间加
我们发现如果区间操作且下传标记时,会破环过去某个时间的状态,因此我们要用到标记永久化。
很多时候标记并不一定要下传,只要在遇到时计算一下贡献就行。
#include<bits/stdc++.h>
#define int long long
#define mid (l+r>>1)
using namespace std;
const int N=1e5+5,INF=1e18;
int n,m,b[N];
int rt[N],tot;
struct node{int ls,rs,val,tag;}a[10000000];
void build(int &p,int l,int r){
p=++tot;
if(l==r)return cin>>a[p].val,void();
build(a[p].ls,l,mid);build(a[p].rs,mid+1,r);
a[p].val=a[a[p].ls].val+a[a[p].rs].val;
}void update(int &p,int lp,int l,int r,int L,int R,int v){
a[p=++tot]=a[lp];a[p].val+=(min(R,r)-max(L,l)+1)*v;
if(L<=l&&r<=R)return a[p].tag+=v,void();
if(L<=mid)update(a[p].ls,a[lp].ls,l,mid,L,R,v);
if(mid<R)update(a[p].rs,a[lp].rs,mid+1,r,L,R,v);
}int query(int p,int l,int r,int L,int R){
if(L<=l&&r<=R)return a[p].val;int res=0;
if(L<=mid)res+=query(a[p].ls,l,mid,L,R);
if(mid<R)res+=query(a[p].rs,mid+1,r,L,R);
return res+a[p].tag*(min(R,r)-max(L,l)+1);
}signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;build(rt[0],1,n);
char op;
int x,y,v,p=0,q=0;
while(m--){
cin>>op>>x;
if(op=='C')cin>>y>>v,b[++q]=++p,update(rt[p],rt[p-1],1,n,x,y,v);
if(op=='Q')cin>>y,cout<<query(rt[p],1,n,x,y)<<'\n';
if(op=='H')cin>>y>>v,cout<<query(rt[b[v]],1,n,x,y)<<'\n';
if(op=='B')rt[++p]=rt[b[q=x]];
}return 0;
}
P3834 静态区间第 \(k\) 小。
可持久化的加入使我们可以多维护一维的信息,我们使用前缀和的思想,有 \(n\) 棵值域线段树(随便离不离散化),第 \(i\) 棵为前 \(i\) 个数构成的“桶”。设我们查询的区间为 \([l,r]\),我们取出第 \(r\) 个桶和第 \(l-1\) 个桶,作差就可以得到区间 \([l,r]\) 的信息。于是在线段树上查找即可。
#include<bits/stdc++.h>
#define mid (l+r>>1)
using namespace std;
const int N=2e5+5,M=1e9;
int n,m,rt[N],tot;
struct node{int ls,rs,w;}a[10000000];
void update(int &p,int lp,int l,int r,int x){
a[p=++tot]=a[lp],++a[p].w;if(l==r)return;
if(x<=mid)update(a[p].ls,a[lp].ls,l,mid,x);
else update(a[p].rs,a[lp].rs,mid+1,r,x);
}int query(int p,int q,int l,int r,int k){
if(l==r)return l;int tmp=a[a[q].ls].w-a[a[p].ls].w;
if(k<=tmp)return query(a[p].ls,a[q].ls,l,mid,k);
else return query(a[p].rs,a[q].rs,mid+1,r,k-tmp);
}signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;int l,r,k;
for(int i=1,x;i<=n;++i)cin>>x,update(rt[i],rt[i-1],0,M,x);
while(m--)cin>>l>>r>>k,cout<<query(rt[l-1],rt[r],0,M,k)<<'\n';
return 0;
}
P2839 [国家集训队] middle
二分答案。将大于等于 \(mid\) 的设为 \(1\),小于的设为 \(-1\)。现在转化成求 \([a,b]\) 的最大后缀和加上 \([c,d]\) 的最大前缀和加上 \((b,c)\) 的区间和。若这个值 \(\ge0\),则 \(mid\) 还可以变大。
考虑到不能暴力维护每个状态,用可持久化线段树维护每种 \(1,-1\) 状态即可。
```#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int root[N],tot;
struct node{int ls,rs,lmax,rmax,sum;}c[N<<5];
inline void push_up(int p){
c[p].sum=c[c[p].ls].sum+c[c[p].rs].sum;
c[p].lmax=max(c[c[p].ls].lmax,c[c[p].ls].sum+c[c[p].rs].lmax);
c[p].rmax=max(c[c[p].rs].rmax,c[c[p].rs].sum+c[c[p].ls].rmax);
}
inline void build(int &p,int l,int r){
p=++tot;
if(l==r){
c[p].lmax=c[p].rmax=c[p].sum=1;return;
}
int mid=(l+r)>>1;
build(c[p].ls,l,mid);
build(c[p].rs,mid+1,r);
push_up(p);
}
inline void update(int &p,int lp,int l,int r,int x,int y){
p=++tot;
c[p]=c[lp];
if(l==r){
c[p].lmax=c[p].rmax=c[p].sum=y;
return;
}
int mid=(l+r)>>1;
if(x<=mid)update(c[p].ls,c[lp].ls,l,mid,x,y);
else update(c[p].rs,c[lp].rs,mid+1,r,x,y);
push_up(p);
}
inline int query_sum(int p,int l,int r,int L,int R){
if(L<=l&&r<=R)
return c[p].sum;
int mid=(l+r)>>1;
if(R<=mid)return query_sum(c[p].ls,l,mid,L,R);
else if(L>mid)return query_sum(c[p].rs,mid+1,r,L,R);
else return query_sum(c[p].ls,l,mid,L,R)+query_sum(c[p].rs,mid+1,r,L,R);
}
inline int query_lmax(int p,int l,int r,int L,int R){
if(L<=l&&r<=R)
return c[p].lmax;
int mid=(l+r)>>1;
if(R<=mid)return query_lmax(c[p].ls,l,mid,L,R);
else if(L>mid)return query_lmax(c[p].rs,mid+1,r,L,R);
else return max(query_lmax(c[p].ls,l,mid,L,R),
query_sum(c[p].ls,l,mid,L,R)+query_lmax(c[p].rs,mid+1,r,L,R));
}
inline int query_rmax(int p,int l,int r,int L,int R){
if(L<=l&&r<=R)
return c[p].rmax;
int mid=(l+r)>>1;
if(R<=mid)return query_rmax(c[p].ls,l,mid,L,R);
else if(L>mid)return query_rmax(c[p].rs,mid+1,r,L,R);
else return max(query_rmax(c[p].rs,mid+1,r,L,R),
query_sum(c[p].rs,mid+1,r,L,R)+query_rmax(c[p].ls,l,mid,L,R));
}
int n,m,pre,q[5];
inline bool check(int x,int a,int b,int c,int d){
int ans=0;
if(b+1<=c-1)ans+=query_sum(root[x],1,n,b+1,c-1);
ans+=query_rmax(root[x],1,n,a,b);
ans+=query_lmax(root[x],1,n,c,d);
return ans>=0;
}
struct num{int x,id;}a[N];
inline bool cmp(num x,num y){return x.x<y.x;}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;++i)
cin>>a[i].x,a[i].id=i;
sort(a+1,a+n+1,cmp);
build(root[1],1,n);
for(int i=2;i<=n;++i)
update(root[i],root[i-1],1,n,a[i-1].id,-1);
cin>>m;
while(m--){
for(int i=1;i<=4;++i)cin>>q[i];
for(int i=1;i<=4;++i)q[i]=(q[i]+pre)%n;
sort(q+1,q+5);
int l=1,r=n;
while(l<r){
int mid=(l+r+1)>>1;
if(check(mid,q[1]+1,q[2]+1,q[3]+1,q[4]+1))l=mid;
else r=mid-1;
}
cout<<a[r].x<<'\n';pre=a[r].x;
}
return 0;
}
P3168 [CQOI2015] 任务查询系统
对于区间加做一个差分,从左到右依次在主席树改就能获得每个时刻的状态,然后在线查询就行了。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+5,M=1e7;
int n,m,tot,lst[N],root[N];
struct upd{int x,y,z;}q[N];
inline bool cmp(upd a,upd b){return a.x<b.x;}
struct node{int w,v,ls,rs;}t[N*40];
inline void update(int &p,int lp,int l,int r,int x,int v){
p=++tot;t[p]=t[lp];
t[p].w+=v,t[p].v+=x*v;
if(l==r)return;
int mid=(l+r)>>1;
if(x<=mid)update(t[p].ls,t[lp].ls,l,mid,x,v);
else update(t[p].rs,t[lp].rs,mid+1,r,x,v);
}
inline int query(int p,int l,int r,int k){
if(l==r)return l*min(t[p].w,k);
int mid=(l+r)>>1;
if(k<=t[t[p].ls].w)return query(t[p].ls,l,mid,k);
else return query(t[p].rs,mid+1,r,k-t[t[p].ls].w)+t[t[p].ls].v;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1,l,r,x;i<=n;++i){
cin>>l>>r>>x;
q[i*2-1]=(upd){l,x,1};
q[i*2]=(upd){r+1,x,-1};
}
sort(q+1,q+n*2+1,cmp);
for(int i=1;i<=(n<<1);++i){
if(q[i].x>n)break;
update(root[i],root[i-1],1,M,q[i].y,q[i].z);
lst[q[i].x]=root[i];
}
for(int i=1;i<=n;++i)
if(lst[i]==0)
lst[i]=lst[i-1];
int pre=1,x,a,b,c;
while(m--){
cin>>x>>a>>b>>c;
int k=(a*pre+b)%c+1;
pre=query(lst[x],1,M,k);
cout<<pre<<'\n';
}
return 0;
}
平衡树
FHQ
这里只推荐 fhq treap。
新建节点与更新点。
inline void push_up(int p){
siz[p]=siz[ls[p]]+siz[rs[p]]+1;
}inline int New(int v){
val[++tot]=v,rnd[tot]=rand(),siz[tot]=1;
return tot;
}
分裂,具体的,将前 \(v\) 个点分到树 \(x\),后若干点分到树 \(y\),这样的作用后面会讲。
inline void split(int p,int v,int &x,int &y){
if(!p){x=y=0;return;}
if(val[p]<=v)split(rs[p],v,rs[x=p],y);
else split(ls[p],v,x,ls[y=p]);
push_up(p);
}
合并两棵树,在合并过程按堆的方式保证深度。
inline int merge(int p,int q){
if(!p||!q)return p|q;
if(rnd[p]<rnd[q])
return ls[q]=merge(p,ls[q]),push_up(q),q;
return rs[p]=merge(rs[p],q),push_up(p),p;
}
平衡树的基本操作,插入和删除。插入时将其裂开,加入点再合并;删除时把值为 \(v\) 的整棵树拿出来,删除根,然后再合并。
inline void insert(int v){
int x=0,y=0;
split(rt,v-1,x,y);
rt=merge(merge(x,New(v)),y);
}inline void erase(int v){
int x=0,y=0,z=0;
split(rt,v,x,z);split(x,v-1,x,y);
rt=merge(merge(x,y=merge(ls[y],rs[y])),z);
}
查询排名与第 \(k\) 小都是平凡的。
inline int kth(int k){
int p=rt;
while(1){
if(k<=siz[ls[p]])p=ls[p];
else if(k==siz[ls[p]]+1)return val[p];
else k-=siz[ls[p]]+1,p=rs[p];
}
}inline int rnk(int v){
int x=0,y=0,ans=0;
split(rt,v-1,x,y);ans=siz[x]+1;
rt=merge(x,y);return ans;
}
前驱后继直接在树上找即可。
inline int pre(int v){
int p=rt,ans=0;
while(1){
if(!p)return ans;
else if(v<=val[p])p=ls[p];
else ans=val[p],p=rs[p];
}
}inline int suf(int v){
int p=rt,ans=0;
while(1){
if(!p)return ans;
else if(v>=val[p])p=rs[p];
else ans=val[p],p=ls[p];
}
}
完整代码。不得不说比其他平衡树好写多了。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int tot,rt;
int ls[N],rs[N],fa[N];
int val[N],rnd[N],siz[N],tag[N];
inline void push_up(int p){
siz[p]=siz[ls[p]]+siz[rs[p]]+1;
}
inline int New(int v){
val[++tot]=v,rnd[tot]=rand(),siz[tot]=1;
return tot;
}
inline void split(int p,int v,int &x,int &y){
if(!p){x=y=0;return;}
if(val[p]<=v)split(rs[p],v,rs[x=p],y);
else split(ls[p],v,x,ls[y=p]);
push_up(p);
}
inline int merge(int p,int q){
if(!p||!q)return p|q;
if(rnd[p]<rnd[q])
return ls[q]=merge(p,ls[q]),push_up(q),q;
return rs[p]=merge(rs[p],q),push_up(p),p;
}
inline void insert(int v){
int x=0,y=0;
split(rt,v-1,x,y);
rt=merge(merge(x,New(v)),y);
}
inline void erase(int v){
int x=0,y=0,z=0;
split(rt,v,x,z);split(x,v-1,x,y);
rt=merge(merge(x,y=merge(ls[y],rs[y])),z);
}
inline int kth(int k){
int p=rt;
while(1){
if(k<=siz[ls[p]])p=ls[p];
else if(k==siz[ls[p]]+1)return val[p];
else k-=siz[ls[p]]+1,p=rs[p];
}
}
inline int rnk(int v){
int x=0,y=0,ans=0;
split(rt,v-1,x,y);ans=siz[x]+1;
rt=merge(x,y);return ans;
}
inline int pre(int v){
int p=rt,ans=0;
while(1){
if(!p)return ans;
else if(v<=val[p])p=ls[p];
else ans=val[p],p=rs[p];
}
}
inline int suf(int v){
int p=rt,ans=0;
while(1){
if(!p)return ans;
else if(v>=val[p])p=rs[p];
else ans=val[p],p=ls[p];
}
}
int n;
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1,op,x;i<=n;++i){
cin>>op>>x;
if(op==1)insert(x);
if(op==2)erase(x);
if(op==3)cout<<rnk(x)<<'\n';
if(op==4)cout<<kth(x)<<'\n';
if(op==5)cout<<pre(x)<<'\n';
if(op==6)cout<<suf(x)<<'\n';
}
return 0;
}
P3391 【模板】文艺平衡树
将一个点的位置作为权值放到平衡树中,如果要翻转一个区间,将这个区间对应的一整棵树取出来,然后打上标记。在标记下传时,交换两个儿子,并且打上标记。
注意两次翻转的标记抵消。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int tot,rt;
int ls[N],rs[N],fa[N];
int val[N],rnd[N],siz[N],tag[N];
inline void push_up(int p){
siz[p]=siz[ls[p]]+siz[rs[p]]+1;
}
inline void push_down(int p){
if(tag[p])
tag[ls[p]]^=1,tag[rs[p]]^=1,
swap(ls[p],rs[p]),tag[p]=0;
}
inline int New(int v){
val[++tot]=v,rnd[tot]=rand(),siz[tot]=1;
return tot;
}
inline void split(int p,int v,int &x,int &y){
if(!p){x=y=0;return;}
push_down(p);
if(siz[ls[p]]>=v)split(ls[p],v,x,ls[y=p]);
else split(rs[p],v-siz[ls[p]]-1,rs[x=p],y);
push_up(p);
}
inline int merge(int p,int q){
if(!p||!q)return p|q;
push_down(p);push_down(q);
if(rnd[p]<rnd[q])
return ls[q]=merge(p,ls[q]),push_up(q),q;
return rs[p]=merge(rs[p],q),push_up(p),p;
}
inline void rever(int l,int r){
int x=0,y=0,z=0;
split(rt,r,x,z),split(x,l-1,x,y);
tag[y]^=1;rt=merge(x,merge(y,z));
}
inline void print(int p){
if(!p)return;
push_down(p);
print(ls[p]),
cout<<val[p]<<' ';
print(rs[p]);
}
int n,m;
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;++i)
rt=merge(rt,New(i));
for(int i=1,l,r;i<=m;++i)
cin>>l>>r,rever(l,r);
print(rt);
return 0;
}
P2042 [NOI2005] 维护数列
平衡树练手题。最大子段和是线段树基本操作,在平衡树上同样适用。
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5,INF=0x3f3f3f3f;
int tot,rt;
int ls[N],rs[N];
int sum[N],val[N],siz[N];
int la[N],ra[N],ma[N];
int rev[N],tag[N],rnd[N];
int sta[N],top;
inline int max(int a,int b,int c){
return max(max(a,b),c);
}
inline void push_up(int p){
siz[p]=siz[ls[p]]+siz[rs[p]]+1;
sum[p]=sum[ls[p]]+val[p]+sum[rs[p]];
la[p]=max(la[ls[p]],sum[ls[p]]+val[p],sum[ls[p]]+val[p]+la[rs[p]]);
ra[p]=max(ra[rs[p]],sum[rs[p]]+val[p],sum[rs[p]]+val[p]+ra[ls[p]]);
ma[p]=max(ma[ls[p]],ma[rs[p]],max(la[p],ra[p],max(ra[ls[p]],0)+val[p]+max(la[rs[p]],0)));
}
inline void make(int p,int op,int k){
if(!p)return;
if(op)
swap(ls[p],rs[p]),
swap(la[p],ra[p]),
rev[p]^=1;
else
val[p]=k,sum[p]=siz[p]*k,
la[p]=ra[p]=ma[p]=max(k,sum[p]),
tag[p]=k;
}
inline void push_down(int p){
if(rev[p])
make(ls[p],1,0),make(rs[p],1,0),rev[p]=0;
if(tag[p]!=INF)
make(ls[p],0,tag[p]),make(rs[p],0,tag[p]),tag[p]=INF;
}
inline int New(int v){
int x=top?sta[top--]:++tot;
val[x]=v,rnd[x]=rand(),siz[x]=1;
sum[x]=la[x]=ra[x]=ma[x]=v;
tag[x]=INF;
return x;
}
inline void Old(int p){
ls[p]=rs[p]=siz[p]=rev[p]=tag[p]=rnd[p]=0;
la[p]=ra[p]=ma[p]=-INF;sum[p]=val[p]=0;
sta[++top]=p;
}
inline void clear(int p){
if(!p)return;
if(ls[p])clear(ls[p]);
if(rs[p])clear(rs[p]);
Old(p);
}
inline void split(int p,int v,int &x,int &y){
if(!p){x=y=0;return;}
push_down(p);
if(siz[ls[p]]>=v)split(ls[p],v,x,ls[y=p]);
else split(rs[p],v-siz[ls[p]]-1,rs[x=p],y);
push_up(p);
}
inline int merge(int p,int q){
if(!p||!q)return p|q;
push_down(p);push_down(q);
if(rnd[p]<rnd[q])
return ls[q]=merge(p,ls[q]),push_up(q),q;
return rs[p]=merge(rs[p],q),push_up(p),p;
}
inline void insert(){
int p,n,x=0,y=0,tr=0;cin>>p>>n;
for(int i=1,x;i<=n;++i)
cin>>x,tr=merge(tr,New(x));
split(rt,p,x,y);rt=merge(merge(x,tr),y);
}
inline void split(int &x,int &y,int &z){
int p,t;cin>>p>>t;--p;
split(rt,p+t,x,z);split(x,p,x,y);
}
inline void erase(){
int x=0,y=0,z=0;split(x,y,z);
clear(y);rt=merge(x,z);
}
inline void rever(){
int x=0,y=0,z=0;split(x,y,z);
make(y,1,0);rt=merge(merge(x,y),z);
}
inline void update(){
int x=0,y=0,z=0,v;split(x,y,z);
cin>>v;
make(y,0,v);
rt=merge(merge(x,y),z);
}
inline void query(){
int x=0,y=0,z=0;split(x,y,z);
cout<<sum[y]<<'\n';
rt=merge(merge(x,y),z);
}
int n,m;
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;
ma[rt]=la[rt]=ra[rt]=-INF;tag[rt]=INF;
for(int i=1,x;i<=n;++i)
cin>>x,rt=merge(rt,New(x));
string s;
for(int i=1;i<=m;++i){
cin>>s;
if(s=="INSERT")insert();
if(s=="DELETE")erase();
if(s=="MAKE-SAME")update();
if(s=="REVERSE")rever();
if(s=="GET-SUM")query();
if(s=="MAX-SUM")cout<<ma[rt]<<'\n';
}
return 0;
}
P4008 [NOI2003] 文本编辑器
更加基础的平衡树,就不放代码了。
P4146 序列终结者
维护两个 \(tag\),很基础,不放代码。
P3960 [NOIP2017 提高组] 列队
很不基础。考虑到我们一次只会更改一行和最后一列,对每行前 \(m-1\) 个开一个平衡树,于是变成了删除和在末尾添加,对最后一列单开一个平衡树,也是删除和在末尾添加。
但是我们不能暴力加入所有点,于是考虑每个点表示一段 \([l,r]\),如果要用到 \(x\in[l,r]\),则将其分裂成 \([l,x-1],[x,x],[x+1,r]\) 即可。
#include<bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
const int N=3e6+5,G=20;
int n,m,q;
int rt[N],tot,rnd[N];
int ls[N],rs[N],la[N],ra[N],siz[N];
int New(int l,int r){
++tot;rnd[tot]=rand(),siz[tot]=(ra[tot]=r)-(la[tot]=l)+1;
return tot;
}void push_up(int p){
siz[p]=siz[ls[p]]+siz[rs[p]]+ra[p]-la[p]+1;
}void split(int p,int v,int &x,int &y){
if(p==0)return x=y=0,void();
if(siz[ls[p]]>=v)split(ls[p],v,x,ls[y=p]);
else split(rs[p],v-siz[p]+siz[rs[p]],rs[x=p],y);
push_up(p);
}int merge(int p,int q){
if(!p||!q)return max(p,q);
if(rnd[p]<rnd[q])
return ls[q]=merge(p,ls[q]),push_up(q),q;
return rs[p]=merge(rs[p],q),push_up(p),p;
}void insert(int x,int l,int r){
rt[x]=merge(rt[x],New(l,r));
}inline pii kth(int p,int k){
int sum=0;
while(1){
if(k<=siz[ls[p]])p=ls[p];
else if(k<=siz[p]-siz[rs[p]])return sum+=siz[ls[p]],make_pair(sum,p);
else k-=siz[p]-siz[rs[p]],sum+=siz[p]-siz[rs[p]],p=rs[p];
}
}void cut(int p,int v){
v=la[p]+v-1;
if(v!=la[p])ls[p]=New(la[p],v-1);
if(v!=ra[p])rs[p]=New(v+1,ra[p]);
la[p]=ra[p]=v;push_up(p);
}int del(int &rt,int v){
int x,y,z;pii res=kth(rt,v);
int v1=res.first,v2=ra[res.second]-la[res.second]+1;
split(rt,v1,x,y);
split(y,v2,y,z);
cut(y,v-v1);
x=merge(x,ls[y]),z=merge(rs[y],z);
rt=merge(x,z);
return la[y];
}signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m>>q;
for(int i=1;i<=n;++i)
insert(i,(i-1)*m+1,i*m-1),insert(n+1,i*m,i*m);
while(q--){
int x,y,ans,tmp;
cin>>x>>y;
if(y!=m){
ans=del(rt[x],y);tmp=del(rt[n+1],x);
insert(x,tmp,tmp),insert(n+1,ans,ans);
}else ans=del(rt[n+1],x),insert(n+1,ans,ans);
cout<<ans<<'\n';
}
return 0;
}
Trie 字典树
字典树
虽然字典树本身不难,但用起来十分灵活。
P4551 最长异或路径
很简单很典,但还是讲一下。
首先将每个点到根的异或和存下来,一条路径 \(u\to v\) 的异或和等于 \(u\to rt\to v\) 路径的异或和。
现在就是从 \(n\) 个数选两个数使其异或值最大。我们将其全部按位从高位到低位插进字典树,对于一个数 \(x\),从高位到低位,如果能往不同的走就往不同的走。复杂度 \(O(n\log n)\)。
#include<bits/stdc++.h>
#define int long long
#define PB push_back
#define MK make_pair
using namespace std;
const int N=100005;
int n,ans,dep[N],tot;
int trie[N<<6][3];
vector<pair<int,int> >g[N];
inline void dfs(int u,int fa){
for(int i=0;i<g[u].size();i++){
int v=g[u][i].first,w=g[u][i].second;
if(v==fa)continue;
dep[v]=dep[u]^w;
dfs(v,u);
}
}
inline void insert(int x){
int p=0;
for(int i=31;i>=0;i--){
if(trie[p][(x>>i)&1]==0)
trie[p][(x>>i)&1]=++tot;
p=trie[p][(x>>i)&1];
}
}
inline int ask(int x){
int p=0,y=0;
for(int i=31;i>=0;i--){
int z=(x>>i)&1;
if(trie[p][z^1])
p=trie[p][z^1],y+=1<<i;
else
p=trie[p][z];
}
return y;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1,u,v,w;i<n;i++)
cin>>u>>v>>w,g[u].PB(MK(v,w)),g[v].PB(MK(u,w));
dfs(1,0);
for(int i=1;i<=n;i++)
insert(dep[i]);
for(int i=1;i<=n;i++)
ans=max(ans,ask(dep[i]));
cout<<ans;
return 0;
}
P5283 [十二省联考 2019] 异或粽子
先做一遍前缀和,转化为求前 \(k\) 大的 \(s_i\oplus s_j\)。由于同一个值会被算两次,故先将 \(k\gets k\times 2\),最后答案除以 \(2\)。我们可以求出对于每一个 \(s_i\) 最优的 \(s_j\),如果 \(s_i\oplus s_j\) 还选不上那么次优的也选不上。
用优先队列存每个 \(s_i\) 目前的最优值,每次取出一个值,计算答案后往后取再加入队列。
剩下的就是怎么求出对于 \(x\) 的第 \(k\) 优选择。在字典树按位存时不仅存有没有,并且存数量。对于某一位的最优方向,如果该方向的数字数大于等于 \(k\) 就往这里走,否则走另一边。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e5+5;
int n,k,a[N];
struct node{int id,w,rank;};
inline bool operator <(node x,node y){return x.w<y.w;}
priority_queue<node> q;
int ch[N*32][2],cnt[N*32],tot;
inline void insert(int x){
int p=0;
for(int i=31;i>=0;--i){
++cnt[p];
if(!ch[p][(x>>i)&1])ch[p][(x>>i)&1]=++tot;
p=ch[p][(x>>i)&1];
}++cnt[p];
}
inline int query(int x,int rank){
int p=0,ans=0;
for(int i=31;i>=0;--i){
int w=(x>>i)&1;
if(!ch[p][w^1])p=ch[p][w];
else if(rank<=cnt[ch[p][w^1]])p=ch[p][w^1],ans+=(1<<i);
else rank-=cnt[ch[p][w^1]],p=ch[p][w];
}
return ans;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>k;k*=2;
for(int i=1;i<=n;++i)
cin>>a[i],a[i]^=a[i-1];
for(int i=0;i<=n;++i)
insert(a[i]);
for(int i=0;i<=n;++i)
q.push((node){i,query(a[i],1),1});
int ans=0;
for(int i=1;i<=k;++i){
node x=q.top();q.pop();ans+=x.w;
if(x.rank<n)q.push((node){x.id,query(a[x.id],x.rank+1),x.rank+1});
}
cout<<ans/2;
return 0;
}
CF241B Friends
是的,\(k\) 没有限制了。
我们可以二分出第 \(k\) 大的数是什么,对于一个值,只需要在字典树上统计即可,这里是 \(O(n\log^2 n)\)。
然后就是统计了。我们发现,大于等于 \(k\) 的一段总是在字典树上占满一整个子树,于是对于字典树每个点开一个 \(\log n\) 的桶,然后就可以按位计算一整个子树的和。
注意第 \(k+1,k+2,...\) 大可能等于第 \(k\) 大。注意空间问题。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=50005,M=N*30,P=1e9+7;
int n,m,k,ans;
int a[N],tot=1;
int ch[M][2],cnt[M];
signed siz[M][31];
void insert(int x){
int p=1;
for(int i=30;i>=0;--i){
++cnt[p];
for(int j=0;j<=30;++j)
siz[p][j]+=(x>>j&1);
if(!ch[p][x>>i&1])
ch[p][x>>i&1]=++tot;
p=ch[p][x>>i&1];
}++cnt[p];
for(int j=0;j<=30;++j)
siz[p][j]+=(x>>j&1);
}int query(int x,int y){
int p=1,res=0;
for(int i=30;i>=0;--i){
if(!p)break;
if(!(y>>i&1))
res+=cnt[ch[p][(x>>i&1)^1]];
p=ch[p][(x^y)>>i&1];
}return res;
}int calcium(int x){
int res=0;
for(int i=1;i<=n;++i)
res+=query(a[i],x);
return res;
}void gty(int x,int p){
for(int j=30;j>=0;--j)
ans+=(int)((x>>j&1)?cnt[p]-siz[p][j]:siz[p][j])<<j;
}void solve(int x,int y){
int p=1;
for(int i=30;i>=0;--i){
if(!p)break;
if(!(y>>i&1))
gty(x,ch[p][!(x>>i&1)]);
p=ch[p][(x^y)>>i&1];
}
}signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m,m*=2;
if(m==0)return cout<<0,0;
for(int i=1;i<=n;++i)
cin>>a[i],insert(a[i]);
int l=0,r=1ll<<31,mid;
while(l+1<r){
mid=l+r>>1;
if(calcium(mid)>=m)l=mid;
else r=mid;
}ans=r*(m-calcium(r));
for(int i=1;i<=n;++i)
solve(a[i],r);
cout<<ans/2%1000000007<<'\n';
return 0;
}
P10218 [省选联考 2024] 魔法手杖
重头戏来了。
看到异或想到字典树是没问题的。
看到异或想到按位贪心答案是没问题的。
一个性质是 \(x\oplus y\le x+y\),因此如果放到 \(S\) 中答案会变优。
假设我们考虑到了 trie 树第 \(d\) 位,且子树内所有数不在 \(S\) 中,我们尽可能使这一位答案为 \(1\)。为了使答案为 \(1\),无论是加法还是异或答案都要为 \(1\)。如果 \(x\) 的第 \(d\) 位填 \(0\),右子树异或上就够了,不用加到 \(S\),左子树要全丢到 \(S\) 中。填 \(1\) 同理。
而丢进 \(S\) 中的条件一是费用足够,二是丢进去后答案的第 \(d\) 位要能够达到 \(1\)。第二个只要求出子树最小值以及 \(x\) 可能的最大值就可以。
注意可以全部加入 \(S\) 的情况,对于边界无非 \(d=-1\) 和空结点,\(x\) 尽可能取大就行了。
#include<bits/stdc++.h>
#define Int __int128
#define int long long
#define ls ch[p][0]
#define rs ch[p][1]
using namespace std;
const int N=1e5+5,K=122;
void read(int &x){
char c;while((c=getchar())<'0'||c>'9');x=c-'0';
while((c=getchar())>='0'&&c<='9')x=x*10+c-'0';
}void read(Int &x){
char c;while((c=getchar())<'0'||c>'9');x=c-'0';
while((c=getchar())>='0'&&c<='9')x=x*10+c-'0';
}void write(Int x){
if(x>9)write(x/10);
putchar(x%10+'0');
}int c,t,n,m,k,tot;
Int a[N],sa[N*K],ans,INF;
int b[N],ch[N*K][2],sb[N*K];
int New(){
sb[++tot]=0,sa[tot]=INF;
ch[tot][0]=ch[tot][1]=0;
return tot;
}void insert(Int x,int y){
int p=1;
for(int i=k-1;i>=0;--i){
sa[p]=min(sa[p],x);
sb[p]+=y;
if(!ch[p][x>>i&1])
ch[p][x>>i&1]=New();
p=ch[p][x>>i&1];
}sa[p]=min(sa[p],x);sb[p]+=y;
}void dfs(int p,int d,int c,Int x,Int y,Int z){
if(d<0)return ans=max(ans,z),void();
Int add=(Int)1<<d,ytg=add-1,gty=(add<<1)-1;
if(!p)return ans=max(ans,y+(x|gty)),void();
bool qhj=0;
if(sb[ls]<=c&&(x|ytg)+min(y,sa[ls])>=(z|add))
dfs(rs,d-1,c-sb[ls],x,min(y,sa[ls]),z|add),qhj=1;
//x_d=0
if(sb[rs]<=c&&(x|gty)+min(y,sa[rs])>=(z|add))
dfs(ls,d-1,c-sb[rs],x|add,min(y,sa[rs]),z|add),qhj=1;
//x_d=1
if(qhj)return;
dfs(ls,d-1,c,x,y,z);
dfs(rs,d-1,c,x|add,y,z);
}signed main(){
read(c),read(t);
while(t--){
read(n),read(m),read(k);
INF=(Int)1<<k;
ans=0;
sa[tot=0]=INF;New();
for(int i=1;i<=n;++i)
read(a[i]);
for(int i=1;i<=n;++i)
read(b[i]),insert(a[i],b[i]);
if(sb[1]>m)dfs(1,k-1,m,0,INF,0);
else ans=sa[1]+INF-1;
write(ans);putchar('\n');
}
return 0;
}

浙公网安备 33010602011771号