蓝书(算法竞赛进阶指南)刷题记录——BZOJ3674可持久化并查集加强版题解(启发式合并+并查集+主席树)

本文深入探讨了在并查集数据结构中实现可持久化的技术,通过使用启发式合并替代路径压缩,解决了操作回溯的问题。文章详细介绍了如何在不显著增加时间和空间复杂度的情况下,维护并查集的历史状态,以便于进行历史操作的查询和恢复。

题目:BZOJ3674.
题目大意:给定 n n n个集合和 m m m个操作,要求支持:
1.格式 1   a   b 1\,a\,b 1ab,表示合并 a , b a,b a,b所在的集合.
2.格式 2   k 2\,k 2k,回到第 k k k次操作之后的状态.
3.格式 3   a   b 3\,a\,b 3ab,询问 a , b a,b a,b是否在同一个集合.
1 ≤ n , m ≤ 2 ∗ 1 0 5 1\leq n,m\leq 2*10^5 1n,m2105,强制在线.

若没有操作2,很明显这是道并查集裸题,但是操作2让这道题的并查集需要可持久化.

考虑如何给并查集可持久化,考虑并查集的过程就只是在维护一个 f a fa fa数组,我们只需要把这个 f a fa fa数组可持久化就行了.

但是路径压缩的并查集每次会修改 O ( log ⁡ n ) O(\log n) O(logn)级别个位置的 f a fa fa数组,使得我们的时空复杂度都是 O ( n log ⁡ 2 n ) O(n\log^2n) O(nlog2n)的,这个空间复杂度有些难以承受.

怎么办呢?考虑用启发式合并代替路径压缩,发现启发式合并的时候每次修改只会改变 O ( 1 ) O(1) O(1)级别个位置的 f a fa fa,只不过要再用可持久化数组维护一下 s i z siz siz,容易发现每次修改 s i z siz siz也只有 O ( 1 ) O(1) O(1)级别个位置,所以空间复杂度降为 O ( n log ⁡ n ) O(n\log n) O(nlogn).

代码如下:

#include<bits/stdc++.h>
  using namespace std;
  
#define Abigail inline void
typedef long long LL;
 
const int N=200000,C=25; 

int n,m;
struct tree{
  int s[2],fa,siz;
}tr[N*C*2+9];
int cn,rot[N+9];
 
void Build(int l,int r,int &k){
  k=++cn;
  if (l==r){tr[k].fa=l;tr[k].siz=1;return;}
  int mid=l+r>>1;
  Build(l,mid,tr[k].s[0]);Build(mid+1,r,tr[k].s[1]);
}
 
void Add_tree_fa(int p,int x,int l,int r,int hk,int &k){
  tr[k=++cn]=tr[hk];
  if (l==r){tr[k].fa=x;return;}
  int mid=l+r>>1;
  if (p<=mid) Add_tree_fa(p,x,l,mid,tr[hk].s[0],tr[k].s[0]);
  else Add_tree_fa(p,x,mid+1,r,tr[hk].s[1],tr[k].s[1]);
}
 
void Add_tree_siz(int p,int x,int l,int r,int hk,int &k){
  tr[k=++cn]=tr[hk];
  if (l==r){tr[k].siz=x;return;}
  int mid=l+r>>1;
  if (p<=mid) Add_tree_siz(p,x,l,mid,tr[hk].s[0],tr[k].s[0]);
  else Add_tree_siz(p,x,mid+1,r,tr[hk].s[1],tr[k].s[1]);
}
 
int Query_fa(int p,int l,int r,int &k){
  if (l==r) return tr[k].fa;
  int mid=l+r>>1;
  if (p<=mid) return Query_fa(p,l,mid,tr[k].s[0]);
  else return Query_fa(p,mid+1,r,tr[k].s[1]);
}
 
int Query_siz(int p,int l,int r,int &k){
  if (l==r) return tr[k].siz;
  int mid=l+r>>1;
  if (p<=mid) return Query_siz(p,l,mid,tr[k].s[0]);
  else return Query_siz(p,mid+1,r,tr[k].s[1]);
}
 
int Find(int x,int id){
  int fa=Query_fa(x,1,n,id);
  return x^fa?Find(fa,id):x;
}
 
Abigail getans(){
  scanf("%d%d",&n,&m);
  Build(1,n,rot[0]);
  int opt,x,y,fx,fy,sx,sy,last=0;
  for (int i=1;i<=m;++i){
    scanf("%d",&opt);
    switch (opt){
      case 1:
        scanf("%d%d",&x,&y);
        x^=last,y^=last;
        fx=Find(x,rot[i-1]);fy=Find(y,rot[i-1]);
        if (fx==fy){rot[i]=rot[i-1];continue;}
        sx=Query_siz(fx,1,n,rot[i-1]);sy=Query_siz(fy,1,n,rot[i-1]);
        if (sx<sy) swap(fx,fy);
        Add_tree_fa(fy,fx,1,n,rot[i-1],rot[i]);
        Add_tree_siz(fx,sx+sy,1,n,rot[i],rot[i]);
        break;
      case 2:
        scanf("%d",&x);
        x^=last;
        rot[i]=rot[x];
        break;
      case 3:
        scanf("%d%d",&x,&y);
        x^=last;y^=last;
        rot[i]=rot[i-1];
        last=Find(x,rot[i])==Find(y,rot[i]);
        printf("%d\n",last);
    }
  } 
}
 
int main(){
  getans();
  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值