可持久化 DS 学习笔记

前言

  • 矮压怎么又是 \(DS\)
  • LZY:为什么没有可持久化
  • msjing:还在咕

可持久化

  • 什么是可持久化捏?
  • 就是利用一些操作,存储曾经的信息而不是覆盖
  • 比如可持久化并查集(不会呢),你就可以查一个点曾经的连通性
  • 有了可持久化,我们就可以进行很多在线操作
  • 那可持久化可以维护什么问题捏?
  • 我也不知道
  • 基本所有在线的暴力过不去的都可以用可持久化过

主席树

  • 主席树全称是可持久化权值线段树,e,我也不知道为啥叫主席树
  • 主席树的实现依据线段树,主要可以维护任意区间的各种玩意
  • 我们看一道典题

例题P3834 【模板】可持久化线段树 2

  • 其实你可以先去做主席树 \(1\)
  • 我是不会告诉你我 void 写成 int 然后没有写返回值交了 inf 发 RE 的
  • 这个问题需要查区间第 \(k\) 小,那么我们可以利用主席树,保留之前的版本,然后就可以方便的查询
  • 怎么记录之前的信息?直接每个版本搞一棵线段树
  • 显然这不太行,所以我们考虑减少一些没必要的操作
  • 我们发现在加入一个新值而产生新状态时,有一些点是不变的,所以我们就可以只建改的点,而这些点是 \(\log\) 级别的,时空都可以接受
  • 从 oiwiki 偷张图

persistent-seg

  • 这个东西明显不能用普通线段树的维护方式搞,所以动态开点
  • 那主席树会搞了,如何求这个问题呢?
  • 我们利用一下前缀和的思想,我们发现求 \([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

  • 其实你观察一下,发现需要在树上搞
  • 那么直接重剖考虑让查变成区间的东西
  • 我们知道主席树有优良的前缀能力,我们可以搞个柿子
$sum[x]+sum[y]−sum[lca]−sum[fa[lca]]$
  • 这个柿子搞完后就可以直接主席树干干干了
  • 这个是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\)

批注 2026-07-22 200359

点击查看代码
#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;
}

可持久化可并堆

  • 其实没啥东西,操作类似主席树操作
  • 这里看

可持久化并查集

  • 这个东西其实没有系统的说法,本质是利用主席树维护可持久化数组实现
  • 我们考虑并查集的可持久化,你发现如果进行可持久化,原来的路径压缩就不能做了,因为是均摊时间复杂度,如果让你返回一个时间复杂度极大的时刻,然后就不动了,显然会很劣,相似的,几乎所有均摊复杂度的都能可持久化
  • 我们必须换一个方法来使复杂度降下来

按秩

  • 说白了就是你钦定一种合并方式使这个东西平衡,从而使朴素上跳找父亲的复杂度是对的,一般有三种
  1. 按深度
  2. 按大小
  3. 随机
  • 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早上又没吃饭不管了
posted @ 2026-07-23 07:53  msjing  阅读(6)  评论(0)    收藏  举报