可持久化 DS 学习笔记
前言
- 矮压怎么又是 \(DS\)
- LZY:为什么没有可持久化
- msjing:还在咕
可持久化
- 什么是可持久化捏?
- 就是利用一些操作,存储曾经的信息而不是覆盖
- 比如可持久化并查集(不会呢),你就可以查一个点曾经的连通性
- 有了可持久化,我们就可以进行很多在线操作
- 那可持久化可以维护什么问题捏?
我也不知道- 基本所有在线的暴力过不去的都可以用可持久化过
主席树
- 主席树全称是可持久化权值线段树,e,我也不知道为啥叫主席树
- 主席树的实现依据线段树,主要可以维护任意区间的各种玩意
- 我们看一道典题
例题P3834 【模板】可持久化线段树 2
- 其实你可以先去做主席树 \(1\)
我是不会告诉你我 void 写成 int 然后没有写返回值交了 inf 发 RE 的- 这个问题需要查区间第 \(k\) 小,那么我们可以利用主席树,保留之前的版本,然后就可以方便的查询
- 怎么记录之前的信息?直接每个版本搞一棵线段树
- 显然这不太行,所以我们考虑减少一些没必要的操作
- 我们发现在加入一个新值而产生新状态时,有一些点是不变的,所以我们就可以只建改的点,而这些点是 \(\log\) 级别的,时空都可以接受
从 oiwiki 偷张图

- 这个东西明显不能用普通线段树的维护方式搞,所以动态开点
- 那主席树会搞了,如何求这个问题呢?
- 我们利用一下前缀和的思想,我们发现求 \([l,r]\) 的区间其实是 \([1,r]\) 减掉 \([1,l-1]\)
我也不知道为什么- 行了现在就搞完了,记得数组开大点
点击查看代码
#include<bits/stdc++.h>
#define lson tr[rt].l
#define rson tr[rt].r
using namespace std;
constexpr int maxn=1e6+10,inf=0x7f7f7f7f;
int read()
{
int x=0,f=1;
char ch=getchar();
while (ch<'0' || ch>'9')
{
if (ch == '-') f=-1;
ch=getchar();
}
while (ch>='0' && ch<='9')
{
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*f;
}
int n,m,a[maxn];
int root[maxn];
int seg;
struct segtree
{
struct _ {int l,r,sum;}tr[maxn*100];
void Pus(int rt) {tr[rt].sum=tr[lson].sum+tr[rson].sum;}
void Upd(int lrt,int &nrt,int l,int r,int p,int v)
{
// cerr << lrt << " "<< nrt << " "<<l << " " <<r << " " << p << " " << v << endl;
if (!nrt) nrt=++seg;
tr[nrt]=tr[lrt];
if (l == r)
{
tr[nrt].sum+=v;
return;
}
int mid=(l+r) >> 1;
if (p<=mid) Upd(tr[lrt].l,tr[nrt].l=++seg,l,mid,p,v);
else Upd(tr[lrt].r,tr[nrt].r=++seg,mid+1,r,p,v);
Pus(nrt);
}
int Que(int lrt,int nrt,int l,int r,int p)
{
if (l == r) return l;
int mid=(l+r) >> 1;
int k=tr[tr[nrt].l].sum-tr[tr[lrt].l].sum;
if (p<=k) return Que(tr[lrt].l,tr[nrt].l,l,mid,p);
else return Que(tr[lrt].r,tr[nrt].r,mid+1,r,p-k);
}
}S;
int main()
{
// freopen("P3834_6.in","r",stdin);
n=read(),m=read();for (int i=1;i<=n;i++) a[i]=read(),S.Upd(root[i-1],root[i],0,1e9,a[i],1);
for (int i=1;i<=m;i++)
{
int x=read(),y=read(),z=read();
printf("%d\n",S.Que(root[x-1],root[y],0,1e9,z));
}
return 0;
}
- 接下来再我们搞几道例题
例题P2633 Count on a tree
- 其实你观察一下,发现需要在树上搞
- 那么
直接重剖考虑让查变成区间的东西 - 我们知道主席树有优良的前缀能力,我们可以搞个柿子
- 这个柿子搞完后就可以直接主席树干干干了
- 这个是sbmqwm改出来的谢谢你喵
- sbmqwm:没事喵
其实sbmqwm 5 秒就改出来了
点击查看代码
#include<bits/stdc++.h>
#define lson tr[rt].l
#define rson tr[rt].r
using namespace std;
constexpr int maxn=1e6+10,inf=0x7f7f7f7f;
int read()
{
int x=0,f=1;
char ch=getchar();
while (ch<'0' || ch>'9')
{
if (ch == '-') f=-1;
ch=getchar();
}
while (ch>='0' && ch<='9')
{
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*f;
}
int n,m,a[maxn],b[maxn];
int root[maxn];
int seg;
int h[maxn],to[maxn],nxt[maxn],tot;
void add(int x,int y) {tot++,to[tot]=y,nxt[tot]=h[x],h[x]=tot;}
struct segtree
{
struct _ {int l,r,sum;}tr[maxn*100];
void Upd(int lrt,int &nrt,int l,int r,int p)
{
nrt=++seg;
tr[nrt]=tr[lrt];tr[nrt].sum++;
if (l == r) return;
int mid=(l+r) >> 1;
if (p<=mid) Upd(tr[lrt].l,tr[nrt].l,l,mid,p);
else Upd(tr[lrt].r,tr[nrt].r,mid+1,r,p);
}
int Que(int lrt,int nrt,int lca,int lcafa,int l,int r,int p)
{
if (l == r) return l;
int mid=(l+r) >> 1;
int k=tr[tr[nrt].l].sum+tr[tr[lrt].l].sum-tr[tr[lca].l].sum-tr[tr[lcafa].l].sum;
if (p<=k) return Que(tr[lrt].l,tr[nrt].l,tr[lca].l,tr[lcafa].l,l,mid,p);
else return Que(tr[lrt].r,tr[nrt].r,tr[lca].r,tr[lcafa].r,mid+1,r,p-k);
}
}S;
struct Tree_Line_Pow_Divide_to_Lca
{
int fa[maxn],son[maxn],top[maxn],dep[maxn],siz[maxn];
int dfn[maxn],rnk[maxn],cnt;
void dfs1(int x)
{
son[x]=-1;siz[x]=1;
S.Upd(root[fa[x]],root[x],1,n,a[x]);
for (int i=h[x];i;i=nxt[i])
{
int y=to[i];
if (dep[y]) continue;
dep[y]=dep[x]+1;
fa[y]=x;
dfs1(y);
siz[x]+=siz[y];
if (son[x] == -1 || siz[y]>siz[son[x]]) son[x]=y;
}
}
void dfs2(int x,int t)
{
top[x]=t;
cnt++;
dfn[x]=cnt;rnk[cnt]=x;
if (son[x] == -1) return;
dfs2(son[x],t);
for (int i=h[x];i;i=nxt[i])
{
int y=to[i];
if (y == son[x] || y == fa[x]) continue;
dfs2(y,y);
}
}
int lca(int x,int y)
{
while (top[x]!=top[y])
{
if (dep[top[x]]<dep[top[y]]) swap(x,y);
x=fa[top[x]];
}
return dep[x]<dep[y]?x:y;
}
}T;
int ans;
int main()
{
n=read(),m=read();for (int i=1;i<=n;i++) a[i]=b[i]=read();
sort(b+1,b+1+n);
int s=unique(b+1,b+1+n)-b-1;
for (int i=1;i<=n;i++) a[i]=lower_bound(b+1,b+1+s,a[i])-b;
for (int i=1;i<n;i++)
{
int x=read(),y=read();
add(x,y);add(y,x);
}
T.dep[1]=1,T.dfs1(1),T.dfs2(1,1);
for (int i=1;i<=m;i++)
{
int x=read()^ans,y=read(),z=read();
int lca=T.lca(x,y);
ans=S.Que(root[x],root[y],root[lca],root[T.fa[lca]],1,n,z);
ans=b[ans];
printf("%d\n",ans);
}
return 0;
}
依旧热衷于重剖求 LCA
例题P4587 [FJOI2016] 神秘数
- 这个东西非常难搞
我们发现毫无思路呢- 我们进行一波非常牛比的转化
- 先考虑什么情况可以使答案增加,对 \(a\) 排序,设当前可以拼出的数都在 \([1,sum]\) 内,当且仅当 \(a_i<=sum+1\) 才可以,此时答案扩展到 \([1,sum+p]\)
- 那这个东西太man了,所以优化优化
- 我们发现我们需要求出在 \([l,r]\) 区间内且值域大小在 \([sum_{pre}+2,sum+1]\) 范围的数的和
- 然后重复上述
- 不好理解对吧,我们模拟一下
- 我们拿题目的序列模拟
- 对于 \(S[1,1,1,4,13]\),首先设 \(sum=0,ans=sum+1=1,pre_sum=-1\)
- 然后,我们查询 \([1,1]\),有 \(3\) 个 \(1\),让 \(sum=3,ans=sum+1=4\)
- 然后查询 \([2,4]\),有 \(1\) 个 \(4\),让 \(sum=3+4=7,ans=sum+1=8\)
- 然后查 \([5,8]\),发现没有数,结束,答案为 \(ans=8\)
- 实现的话你就没必要模拟了,直接让 \(sum = que([1,sum+1])\)
- 然后没啥了
- 我不会告诉你我上面传 \(1\) 下面查 \(0\) 的

点击查看代码
#include<bits/stdc++.h>
#define lson tr[rt].l
#define rson tr[rt].r
using namespace std;
constexpr int maxn=1e6+10,inf=0x7f7f7f7f;
int read()
{
int x=0,f=1;
char ch=getchar();
while (ch<'0' || ch>'9')
{
if (ch == '-') f=-1;
ch=getchar();
}
while (ch>='0' && ch<='9')
{
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*f;
}
int n,m,a[maxn];
int root[maxn];
int seg;
struct segtree
{
struct _ {int l,r,sum;}tr[maxn*100];
void Upd(int lt,int &nw,int l,int r,int p,int v)
{
nw=++seg;
tr[nw]=tr[lt];
tr[nw].sum+=v;
if (l == r) return;
int mid=(l+r) >> 1;
if (p<=mid) Upd(tr[lt].l,tr[nw].l,l,mid,p,v);
else Upd(tr[lt].r,tr[nw].r,mid+1,r,p,v);
}
int Que(int lt,int nw,int l,int r,int L,int R)
{
int res=0;
if (L<=l && r<=R) return tr[nw].sum-tr[lt].sum;
int mid=(l+r) >> 1;
if (L<=mid) res+=Que(tr[lt].l,tr[nw].l,l,mid,L,R);
if (R>mid) res+=Que(tr[lt].r,tr[nw].r,mid+1,r,L,R);
return res;
}
}S;
int main()
{
n=read();for (int i=1;i<=n;i++) a[i]=read(),S.Upd(root[i-1],root[i],1,1e9,a[i],a[i]);
m=read();
for (int i=1;i<=m;i++)
{
int x=read(),y=read();
int sum=0,ans=1;
while (78+13 == 91)
{
sum=S.Que(root[x-1],root[y],1,1e9,1,ans);
if (sum>=ans) ans=sum+1;
else break;
}
printf("%d\n",ans);
}
return 0;
}
可持久化 0/1 Trie
- 可持久化 \(Trie\) 又称可持久化字典树,通常以可持久化 \(0/1\) \(Trie\) 出现,多用于解决异或(\(xor\))问题
- 我们还是结合例题看
例题P4735 最大异或和
- 有两个操作,加一个数和求区间异或最大
- 我们考虑异或的性质,想让异或和最大,就要让异或的两个数尽可能不同并且要让尽可能高的二进制位为 \(1\),那么从上到下支持高位贪心的
\(0/1\) \(Trie\) 就是非常好的 \(DS\) 来维护这个问题,发现区间的查询,可以类比主席树上可持久化 - 欸,异或有个先天优势:支持前缀,那随便艹了
- 代码贺的 oiwiki 的,非常清晰
点击查看代码
#include<bits/stdc++.h>
using namespace std;
constexpr int maxn=6e5+10,inf=0x7f7f7f7f;
int read()
{
int x=0,f=1;
char ch=getchar();
while (ch<'0' || ch>'9')
{
if (ch == '-') f=-1;
ch=getchar();
}
while (ch>='0' && ch<='9')
{
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*f;
}
int n,m;
int a[maxn],s[maxn];
struct Trie
{
int rt[maxn],cnt,ch[maxn*35][2],v[maxn*35];
void ins(int x,int lt,int val)
{
for (int i=28;i>=0;i--)
{
v[x]=v[lt]+1;
if ((val&(1<<i)) == 0)
{
if (!ch[x][0]) ch[x][0]=++cnt;
ch[x][1]=ch[lt][1];
x=ch[x][0];
lt=ch[lt][0];
}
else
{
if (!ch[x][1]) ch[x][1]=++cnt;
ch[x][0]=ch[lt][0];
x=ch[x][1];
lt=ch[lt][1];
}
}
v[x]=v[lt]+1;
}
int que(int x1,int x2,int val)
{
int res=0;
for (int i=28;i>=0;i--)
{
int t=(val&(1<<i))?1:0;
if (v[ch[x1][!t]]-v[ch[x2][!t]]) res+=(1<<i),x1=ch[x1][!t],x2=ch[x2][!t];
else x1=ch[x1][t],x2=ch[x2][t];
}
return res;
}
}T;
int main()
{
n=read(),m=read();for (int i=1;i<=n;i++) a[i]=read(),s[i]=s[i-1]^a[i];
for (int i=1;i<=n;i++) T.rt[i]=++T.cnt,T.ins(T.rt[i],T.rt[i-1],s[i]);
while (m--)
{
char op=getchar();
while (op<'A' || op>'Z') op=getchar();
if (op == 'A')
{
n++;
a[n]=read();
s[n]=s[n-1]^a[n];
T.rt[n]=++T.cnt;
T.ins(T.rt[n],T.rt[n-1],s[n]);
}
else
{
int l=read(),r=read(),x=read();
l--,r--;
if (l == 0) printf("%d\n",max(s[n]^x,T.que(T.rt[r],T.rt[0],s[n]^x)));
else printf("%d\n",T.que(T.rt[r],T.rt[l-1],s[n]^x));
}
}
return 0;
}
例题P5283 [十二省联考 2019] 异或粽子
- upd on 26.8.18:
- 我艹屮艸芔茻怎么降 \(\color{blue}{蓝}\) 了
- 其实这题是个 \(trick\) 但是msjing不会做
- 可以去 9G 学长那里看看
- 建议先做P2048 [NOI2010] 超级钢琴
- 链一下题解
- 我们考虑求 \(k\) 个最大,一个很好搞,直接可持久化 \(Trie\) 高位贪心创过去
- 对于一个最优决策,会把一个值域区间分成两块,考虑固定右端点,找到一个左端点满足最优决策,把其前后分出的两区间加入堆中,同理继续做,并不断枚举右端点,复杂度好像是对的
点击查看代码
#include<bits/stdc++.h>
#define int long long
//using 1=1ll;
using namespace std;
constexpr int maxn=5e5+10,inf=0x7f7f7f7f;
int read()
{
int x=0,f=1;
char ch=getchar();
while (ch<'0' || ch>'9')
{
if (ch == '-') f=-1;
ch=getchar();
}
while (ch>='0' && ch<='9')
{
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*f;
}
int n,k;
int a[maxn];
int cn=1;
struct Trie
{
struct _ {int ch[2],cnt;}tr[maxn*40];
void ins(int v)
{
int nw=1;
tr[nw].cnt++;
for (int i=33;i>=0;i--)
{
int t=(v>>i)&1ll;
if (!tr[nw].ch[t]) tr[nw].ch[t]=++cn;
nw=tr[nw].ch[t];
tr[nw].cnt++;
}
}
int que(int v,int k)
{
int nw=1,ans=0;
for (int i=33;i>=0;i--)
{
int t=(v>>i)&1ll;
if (tr[tr[nw].ch[t]].cnt>=k) nw=tr[nw].ch[t];
else k-=tr[tr[nw].ch[t]].cnt,nw=tr[nw].ch[t^1],ans+=(1ll<<i);
}
return ans;
}
}T;
struct __
{
int id,rk,v;
bool operator < (const __ &a)const {return v<a.v;}
};
priority_queue<__> q;
int ans;
signed main()
{
n=read(),k=read();k*=2;T.ins(0);
for (int i=1;i<=n;i++) a[i]=read()^a[i-1],T.ins(a[i]);
for (int i=0;i<=n;i++) q.push({i,n+1,T.que(a[i],n+1)});
while (k--)
{
__ x=q.top();q.pop();
ans+=x.v;
if (x.rk) q.push({x.id,x.rk-1,T.que(a[x.id],x.rk-1)});
}
printf("%lld\n",ans/2ll);
return 0;
}
可持久化平衡树
- 可持久化平衡树的基础是 \(FHQ\),没学过可以去我这学一下
- 其实 \(WBLT\) 好像也可以做,但是msjing不会喵
- 不像一些带旋的平衡树,\(FHQ\) 由于其独特的 \(split\) 和 \(merge\),可以做可持久化
例题
- 我们考虑进行可持久化的变形,但是msjing发现这个好像就是在 \(FHQ\) 板子上改改改?
- 我们要进行可持久化操作,就要放弃直接修改,而是再建一个点,在这上面修
- 复制节点的代码很好写,你用结构体甚至直接赋就行
点击查看代码
void cpy(int x,int y)
{
val[x]=val[y],siz[x]=siz[y],rnd[x]=rnd[y];
ls[x]=ls[y],rs[x]=rs[y];
}
- \(split\) 和 \(merge\) 就是加上了复制节点的过程
点击查看代码
void Split(int now,int v,int &x,int &y)
{
if (!now) return x=y=0,void();
int nw=++tot;cpy(nw,now);
if (val[now]<=v) x=nw,Split(rs[now],v,rs[nw],y);
else y=nw,Split(ls[now],v,x,ls[nw]);
Pushup(nw);
}
int Merge(int x,int y)
{
if (!x || !y) return x|y;
int nw=++tot;
if (rnd[x]<rnd[y])
{
cpy(nw,x);
rs[nw]=Merge(rs[nw],y);
Pushup(nw);
return nw;
}
else
{
cpy(nw,y);
ls[nw]=Merge(x,ls[nw]);
Pushup(nw);
return nw;
}
}
- 新建还是那样,\(insert\) 和 \(delete\) 有点改动
点击查看代码
int New(int v)
{
tot++,val[tot]=v,rnd[tot]=rd(),siz[tot]=1;
return tot;
}
void Ins(int &rt,int a)
{
if (!rt) return rt=New(a),void();
int x,y;
Split(rt,a,x,y);
rt=Merge(Merge(x,New(a)),y);
}
void Del(int &rt,int v)
{
int x,y,z;
Split(rt,v,x,z);
Split(x,v-1,x,y);
if (y)
{
int nw=Merge(ls[y],rs[y]);
rt=Merge(x,Merge(nw,z));
}
else rt=Merge(x,z);
}
- \(siz\) 和 \(kth\) 没啥区别
点击查看代码
int Siz(int rt,int v)
{
if(!rt) return 0;
if(v<val[rt]) return Siz(ls[rt],v);
else return siz[ls[rt]]+Siz(rs[rt],v)+1;
}
int Kth(int x,int k)
{
while (1)
{
if (k<=siz[ls[x]]) x=ls[x];
else if (k == siz[ls[x]]+1) return val[x];
else k-=(siz[ls[x]]+1),x=rs[x];
}
}
- \(pre\) 和 \(nxt\) msjing大改了一下,还是那个原理
点击查看代码
int Pre(int rt,int v)
{
if (!rt) return -inf;
if(val[rt]<v) return max(val[rt],Pre(rs[rt],v));
else return Pre(ls[rt],v);
}
int Nxt(int rt,int v)
{
if (!rt) return inf;
if(val[rt]>v) return min(val[rt],Nxt(ls[rt],v));
else return Nxt(rs[rt],v);
}
- 总代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
constexpr int maxn=4e7+10,inf=0x7fffffff;
int read()
{
int x=0,f=1;
char ch=getchar();
while (ch<'0'||ch>'9')
{
if (ch=='-') f=-1;
ch=getchar();
}
while (ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch-'0');
ch=getchar();
}
return x*f;
}
int n,root[maxn];
namespace FHQ
{
mt19937 rd(time(0));
int val[maxn],siz[maxn],rnd[maxn],tot,ls[maxn],rs[maxn];
void cpy(int x,int y)
{
val[x]=val[y],siz[x]=siz[y],rnd[x]=rnd[y];
ls[x]=ls[y],rs[x]=rs[y];
}
void Pushup(int x) {siz[x]=siz[ls[x]]+siz[rs[x]]+1;}
void Split(int now,int v,int &x,int &y)
{
if (!now) return x=y=0,void();
int nw=++tot;cpy(nw,now);
if (val[now]<=v) x=nw,Split(rs[now],v,rs[nw],y);
else y=nw,Split(ls[now],v,x,ls[nw]);
Pushup(nw);
}
int Merge(int x,int y)
{
if (!x || !y) return x|y;
int nw=++tot;
if (rnd[x]<rnd[y])
{
cpy(nw,x);
rs[nw]=Merge(rs[nw],y);
Pushup(nw);
return nw;
}
else
{
cpy(nw,y);
ls[nw]=Merge(x,ls[nw]);
Pushup(nw);
return nw;
}
}
int New(int v)
{
tot++,val[tot]=v,rnd[tot]=rd(),siz[tot]=1;
return tot;
}
void Ins(int &rt,int a)
{
if (!rt) return rt=New(a),void();
int x,y;
Split(rt,a,x,y);
rt=Merge(Merge(x,New(a)),y);
}
void Del(int &rt,int v)
{
int x,y,z;
Split(rt,v,x,z);
Split(x,v-1,x,y);
if (y)
{
int nw=Merge(ls[y],rs[y]);
rt=Merge(x,Merge(nw,z));
}
else rt=Merge(x,z);
}
int Siz(int rt,int v)
{
if(!rt) return 0;
if(v<val[rt]) return Siz(ls[rt],v);
else return siz[ls[rt]]+Siz(rs[rt],v)+1;
}
int Kth(int x,int k)
{
while (1)
{
if (k<=siz[ls[x]]) x=ls[x];
else if (k == siz[ls[x]]+1) return val[x];
else k-=(siz[ls[x]]+1),x=rs[x];
}
}
int Pre(int rt,int v)
{
if (!rt) return -inf;
if(val[rt]<v) return max(val[rt],Pre(rs[rt],v));
else return Pre(ls[rt],v);
}
int Nxt(int rt,int v)
{
if (!rt) return inf;
if(val[rt]>v) return min(val[rt],Nxt(ls[rt],v));
else return Nxt(rs[rt],v);
}
}using namespace FHQ;
int main()
{
n=read();
root[0]=0;
for (int i=1;i<=n;i++)
{
int v=read(),op=read(),x=read();
root[i]=root[v];
if (op == 1) Ins(root[i],x);
if (op == 2) Del(root[i],x);
if (op == 3) printf("%d\n",Siz(root[i],x-1)+1);
if (op == 4) printf("%d\n",Kth(root[i],x));
if (op == 5) printf("%d\n",Pre(root[i],x));
if (op == 6) printf("%d\n",Nxt(root[i],x));
}
return 0;
}
可持久化可并堆
- 其实没啥东西,操作类似主席树操作
- 这里看
可持久化并查集
- 这个东西其实没有系统的说法,本质是利用主席树维护可持久化数组实现
- 我们考虑并查集的可持久化,你发现如果进行可持久化,原来的路径压缩就不能做了,因为是均摊时间复杂度,如果让你返回一个时间复杂度极大的时刻,然后就不动了,显然会很劣,相似的,几乎所有均摊复杂度的都能可持久化
- 我们必须换一个方法来使复杂度降下来
按秩
- 说白了就是你钦定一种合并方式使这个东西平衡,从而使朴素上跳找父亲的复杂度是对的,一般有三种
- 按深度
- 按大小
- 随机
- e,随机被卡掉了
- 我们说按深度的做法,这个简单
- 首先我们考虑在一浅一深两个并查集的合并中如何保证深度
- 我们发现小向大合并会使深度最多加 \(1\)
- 为什么?
- 设两个并查集为 \(x\) 和 \(y\)
- 若深度相同,\(y\) 根会接到 \(x\) 根后,相当于是把 \(x\) 向下推了一个深度,合并后深度 \(+1\)
- 若深度不同,接上后无影响,反正最深的还是最深的
- 就没了
- 主席树维护一下做
- 看不懂的话请前往模板题题解处,神犇讲的比msjing好多了
代码找一个喜欢的板子贺
点击查看代码
#include <bits/stdc++.h>
using namespace std;
constexpr int maxn=2e5+10;
int read()
{
int x=0,f=1;
char ch=getchar();
while (ch<'0' || ch>'9')
{
if (ch == '-') f=-1;
ch=getchar();
}
while (ch>='0' && ch<='9')
{
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*f;
}
int n,m;
namespace hjt_Tree_and_dsu
{
int root[maxn];
struct _ {int ls,rs,fa,dep;}tr[maxn*50];
#define mid ((l+r)>>1)
int seg;
int New(int x)
{
tr[++seg]=tr[x];
return seg;
}
int Build(int l,int r)
{
int rt=++seg;
if (l == r)
{
tr[rt].fa=l;
return rt;
}
tr[rt].ls=Build(l,mid),tr[rt].rs=Build(mid+1,r);
return rt;
}
int Upd(int x,int l,int r,int p,int fa)
{
int rt=New(x);
if (l == r)
{
tr[rt].fa=fa;
return rt;
}
if (p<=mid) tr[rt].ls=Upd(tr[x].ls,l,mid,p,fa);
else tr[rt].rs=Upd(tr[x].rs,mid+1,r,p,fa);
return rt;
}
int que(int rt,int l,int r,int p)
{
if (l == r) return rt;
if (p<=mid) return que(tr[rt].ls,l,mid,p);
else return que(tr[rt].rs,mid+1,r,p);
}
int fid(int x,int a)
{
int fa=que(root[x],1,n,a);
if (tr[fa].fa == a) return fa;
return fid(x,tr[fa].fa);
}
int add(int x,int l,int r,int p)
{
int rt=New(x);
if (l == r)
{
tr[rt].dep++;
return rt;
}
if (p<=mid) tr[rt].ls=add(tr[x].ls,l,mid,p);
else tr[rt].rs=add(tr[x].rs,mid+1,r,p);
return rt;
}
void merge(int x,int a,int b)
{
root[x]=root[x-1];
a=fid(x,a),b=fid(x,b);
if (tr[a].fa!=tr[b].fa)
{
if (tr[a].dep>tr[b].dep) swap(a,b);
root[x]=Upd(root[x-1],1,n,tr[a].fa,tr[b].fa);
if (tr[a].dep == tr[b].dep)
root[x]=add(root[x],1,n,tr[b].fa);
}
}
int judge(int x,int a,int b)
{
a=fid(x,a),b=fid(x,b);
return (tr[a].fa == tr[b].fa);
}
}using namespace hjt_Tree_and_dsu;
int main()
{
n=read(),m=read();
root[0]=Build(1,n);
for (int i=1;i<=m;i++)
{
int op=read();
if (op == 1)
{
int x=read(),y=read();
merge(i,x,y);
}
else if (op == 2)
{
int x=read();
root[i]=root[x];
}
else
{
int x=read(),y=read();
if (judge(i-1,x,y)) puts("1");
else puts("0");
root[i]=root[i-1];
}
}
return 0;
}
后话
- msjing on 26.7.23:woc早上又没吃饭不管了

浙公网安备 33010602011771号