可持久化
基操
可以回溯修改前的东西
压缩空间
每次新开节点记录信息,代码极简
模板0
过于简单没有代码
可持久化栈
每次新开节点,回溯时还原直接回到之前的节点
模板1
求区间第k小
将权值线段树yy成数组,这无疑是前缀和相减后二分,所以可持久化
#include<bits/stdc++.h>
using namespace std;
const int N=1e7+5;
int n,m,cnt,c[N],ls[N],rs[N],rt[N];
void bld(int &p,int l,int r,int x) {
++cnt; c[cnt]=c[p]+1,ls[cnt]=ls[p],rs[cnt]=rs[p];
p=cnt;
if(l==r) return;
int mid=l+(r-l>>1);
if(x<=mid) bld(ls[p],l,mid,x);
else bld(rs[p],mid+1,r,x);
}
int ask(int p1,int p2,int l,int r,int x) {
if(l==r) {
return l;
}
int mid=l+(r-l>>1),d=c[ls[p1]]-c[ls[p2]];
if(d>=x) return ask(ls[p1],ls[p2],l,mid,x);
return ask(rs[p1],rs[p2],mid+1,r,x-d);
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) {
int t; scanf("%d",&t); t+=1e9;
rt[i]=rt[i-1],bld(rt[i],0,2e9,t);
}
for(int i=1;i<=m;i++) {
int l,r,k; scanf("%d%d%d",&l,&r,&k);
printf("%d\n",ask(rt[r],rt[l-1],0,2e9,k)-(int)1e9);
}
return 0;
}
模板2
见fhq-treap
模板3
和区间第K大一毛一样
模板4
不能路径压缩,必须按秩合并,一个\(lg\)
每次只会改一个父亲,顶多再改一个深度
所以可以用可持久化线段树维护,再一个\(lg\)
注意返回的最好是节点编号(量大好使)
#include<bits/stdc++.h>
using namespace std;
const int M=2e5+5,N=7e6+5;
int n,m,rt[M],ls[N],rs[N],cnt;
struct A {int fa,dep; }c[N];
void bld(int &p,int l,int r) {
p=++cnt;
if(l==r) {
c[p]=(A){l,1}; return;
}
int mid=l+r>>1;
bld(ls[p],l,mid),bld(rs[p],mid+1,r);
}
void upd(int &p,int l,int r,int x,A k) {
ls[++cnt]=ls[p],rs[cnt]=rs[p];
p=cnt;
if(l==r) {
c[p]=k; return;
}
int mid=l+r>>1;
if(x<=mid) upd(ls[p],l,mid,x,k);
else upd(rs[p],mid+1,r,x,k);
}
int ask(int p,int l,int r,int x) {
if(l==r) return p;
int mid=l+r>>1;
if(x<=mid) return ask(ls[p],l,mid,x);
return ask(rs[p],mid+1,r,x);
}
int find(int p,int x) {
int t=ask(p,1,n,x);
for(;c[t].fa!=x;x=c[t].fa,t=ask(p,1,n,x));
return t;
}
int main(){
scanf("%d%d",&n,&m);
bld(rt[0],1,n);
for(int i=1;i<=m;i++) {
int op; scanf("%d",&op);
if(op==1) {
int u,v; scanf("%d%d",&u,&v);
int x=find(rt[i-1],u),y=find(rt[i-1],v);
rt[i]=rt[i-1];
if(c[x].dep>c[y].dep) {
upd(rt[i],1,n,c[y].fa,c[x]);
} else if(c[x].dep==c[y].dep) {
upd(rt[i],1,n,c[y].fa,c[x]);
upd(rt[i],1,n,c[x].fa,(A){c[x].fa,c[x].dep+1});
} else upd(rt[i],1,n,c[x].fa,c[y]);
} else if(op==2) {
int k; scanf("%d",&k);
rt[i]=rt[k];
} else {
int u,v; scanf("%d%d",&u,&v);
rt[i]=rt[i-1];
int x=find(rt[i],u),y=find(rt[i],v);
puts(c[x].fa==c[y].fa?"1":"0");
}
}
return 0;
}
例题1
第\(K\)小=权值线段树
维护\(u\)到根的所有点权的权值线段树为\(rt(u)\),设\(u\)->\(v\)权值线段树为\(Rt(u,v)\)
\[Rt(u,v)=rt(u)+rt(v)-rt(lca)-rt(fa(lca))
\]
注意是\(rt(lca)\)和\(rt(fa(lca))\),\(lca\)需要算1遍
#include<bits/stdc++.h>
using namespace std;
const int N=4e6+5,M=1e5+5;
int n,m,c[N],ls[N],rs[N],cnt;
int rt[M],f[M][21],lg[M],d[M],a[M],b[M],fid[M];
vector<int>V[M];
void add(int &p,int l,int r,int x) {
c[++cnt]=c[p]+1,ls[cnt]=ls[p],rs[cnt]=rs[p];
p=cnt;
if(l==r) return;
int mid=l+r>>1;
if(x<=mid) add(ls[p],l,mid,x);
else add(rs[p],mid+1,r,x);
}
void dfs(int fa,int u) {
rt[u]=rt[fa],add(rt[u],1,n,a[u]);
d[u]=!fa?0:d[fa]+1,f[u][0]=fa;
for(int i=1;i<=lg[d[u]];i++) {
f[u][i]=f[f[u][i-1]][i-1];
}
for(auto v:V[u]) {
if(v!=fa) dfs(u,v);
}
}
inline int lca(int u,int v) {
if(d[u]>d[v]) swap(u,v);
while(d[u]<d[v]) {
v=f[v][lg[d[v]-d[u]]];
}
if(u==v) return u;
for(int i=lg[d[u]];i>=0;i--) {
if(f[u][i]!=f[v][i]) {
u=f[u][i],v=f[v][i];
}
}
return f[u][0];
}
inline bool cmp(int i,int j) {
return a[i]<a[j];
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) {
scanf("%d",&a[i]),b[i]=i;
}
sort(b+1,b+n+1,cmp);
fid[1]=a[b[1]]; a[b[1]]=1; int tot=1;
for(int i=2;i<=n;i++) {
if(a[b[i]]>fid[a[b[i-1]]]) {
++tot,fid[tot]=a[b[i]];
}
a[b[i]]=tot;
}
for(int i=1;i<n;i++) {
int u,v; scanf("%d%d",&u,&v);
V[u].push_back(v),V[v].push_back(u);
}
lg[0]=-1;
for(int i=1;i<=n;i++) lg[i]=lg[i>>1]+1;
dfs(0,1); int ans=0;
while(m--) {
int u,v,k; scanf("%d%d%d",&u,&v,&k); u^=ans;
int lc=lca(u,v);
int l=1,r=n,mid,ret,n1=rt[u],n2=rt[v],n3=rt[lc],n4=rt[f[lc][0]];
while(l<r) {
mid=l+r>>1;
ret=c[ls[n1]]+c[ls[n2]]-c[ls[n3]]-c[ls[n4]];
if(ret>=k) {
r=mid;
n1=ls[n1],n2=ls[n2],n3=ls[n3],n4=ls[n4];
} else {
l=mid+1,k-=ret;
n1=rs[n1],n2=rs[n2],n3=rs[n3],n4=rs[n4];
}
}
printf("%d\n",ans=fid[l]);
}
return 0;
}
例题2
中位数通常可以二分答案,然后标记$\geq \(它的为1,比它小的为-1,则序列的中位数一定是满足序列和\)\geq$0的最大数(等号因为相同取后者)
所以可以二分答案,标记,统计中间的和\([b,c]\)+左边的最大前缀(包括不选)+右边的最小前缀(包括不选)
我可能知道为什么我数据结构老要重构了,因为开始时没想好全部,导致中间改得不完整
要不以后还是写两遍吧
#include<bits/stdc++.h>
using namespace std;
const int N=2e4+5,M=1e6+5;
int n,m,a[N],b[N],fid[N],rt[N];
int c[M],ls[M],rs[M],lc[M],rc[M],cnt;
inline bool cmp(int i,int j) {
return a[i]<a[j];
}
inline void up(int p) {
c[p]=c[ls[p]]+c[rs[p]];
lc[p]=max(lc[ls[p]],c[ls[p]]+lc[rs[p]]);
rc[p]=max(rc[rs[p]],c[rs[p]]+rc[ls[p]]);
}
void add(int &p,int l,int r,int x,int k) {
c[++cnt]=c[p],ls[cnt]=ls[p],rs[cnt]=rs[p];
p=cnt;
if(l==r) {
c[p]+=k; lc[p]=rc[p]=max(0,c[p]);
return;
}
int mid=l+r>>1;
if(x<=mid) add(ls[p],l,mid,x,k);
else add(rs[p],mid+1,r,x,k);
up(p);
}
int sum(int p,int l,int r,int x,int y) {
if(!p||x>y) return 0;
if(l==x&&r==y) return c[p];
int mid=l+r>>1;
if(y<=mid) return sum(ls[p],l,mid,x,y);
if(x>mid) return sum(rs[p],mid+1,r,x,y);
return sum(ls[p],l,mid,x,mid)+sum(rs[p],mid+1,r,mid+1,y);
}
int pre(int p,int l,int r,int x,int y) {
if(!p) return 0;
if(l==x&&r==y) return lc[p];
int mid=l+r>>1;
if(y<=mid) return pre(ls[p],l,mid,x,y);
if(x>mid) return pre(rs[p],mid+1,r,x,y);
return max(pre(ls[p],l,mid,x,mid),sum(ls[p],l,mid,x,mid)+pre(rs[p],mid+1,r,mid+1,y));
}
int suf(int p,int l,int r,int x,int y) {
if(!p) return 0;
if(l==x&&r==y) return rc[p];
int mid=l+r>>1;
if(y<=mid) return suf(ls[p],l,mid,x,y);
if(x>mid) return suf(rs[p],mid+1,r,x,y);
return max(suf(rs[p],mid+1,r,mid+1,y),sum(rs[p],mid+1,r,mid+1,y)+suf(ls[p],l,mid,x,mid));
}
int q[5];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) {
scanf("%d",&a[i]); b[i]=i;
}
sort(b+1,b+n+1,cmp);
for(int i=1;i<=n;i++) {
add(rt[1],1,n,i,1);
}
fid[1]=a[b[1]]; a[b[1]]=1,m=1;
rt[2]=rt[1],add(rt[2],1,n,b[1],-2);
for(int i=2;i<=n;i++) {
if(a[b[i]]>fid[a[b[i-1]]]) {
fid[++m]=a[b[i]],rt[m+1]=rt[m];
}
a[b[i]]=m; add(rt[m+1],1,n,b[i],-2);
}
int T; scanf("%d",&T); int ans=0;
while(T--) {
scanf("%d%d%d%d",&q[0],&q[1],&q[2],&q[3]);
for(int i=0;i<4;i++) q[i]=(q[i]+ans)%n+1;
sort(q,q+4);
int l=1,r=m,mid,ret;
while(l<=r) {
mid=l+r>>1;
ret=sum(rt[mid],1,n,q[1],q[2])+suf(rt[mid],1,n,q[0],q[1]-1)+pre(rt[mid],1,n,q[2]+1,q[3]);
if(ret>=0) {
ans=mid,l=mid+1;
} else r=mid-1;
}
printf("%d\n",fid[ans]);
ans=fid[ans];
}
return 0;
}