lemonoil


OI__nothing is impossible


可持久化并查集(三)——从动态到可持久化

其实在上一篇:可持久化并查集(二)——从镜像到动态

中已经可以算是一个伪可持久化了,但是对于历史查询等操作无能为力。。所以本篇重点介绍可持久化操作 这里写图片描述

BZOJ 3673

题目连接

这题不知道出题人什么做法,但是代码很短的样子 UPD:出题人用的是rope,即stl中的可持久化平衡树 KuribohG神犇告诉了我可以用可持久化线段树实现可持久化数组T T 既然都有可持久化数组了,只要用个再并查集的启发式合并就能妥妥的水过了(这样每次只要修改一个fa) 并查集的启发式合并就是按秩合并,初始所有集合秩为0 合并把秩小的树根的父亲设为秩大的树根 如果秩相同,则随便选取一个作为父节点并将它的秩+1 秩和fa一样维护 但是其实这题数据随机的话随便合并就行了,根本不用按秩合并什么的 UPD:秩其实有的时候很不好用,维护子树大小比较赞。。。 另外,ndsf发现只要直接暴力就能虐了T T 引用自hzwer

所以可以发现暴力出奇迹大力发展可持久化并查集是解决当今膜蛤事件。。。的唯一方法!! 最终我们还是写份正解(hfu说没有正解,只有更好的暴力)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include<cstdio>
#include<iostream>
#define N 2000005
using namespace std;
int n,m,sz;
int root[N],ls[N],rs[N],v[N],deep[N];
void build(int &k,int l,int r){
if(!k)k=++sz;
if(l==r){v[k]=l;return;}
int mid=(l+r)>>1;
build(ls[k],l,mid);
build(rs[k],mid+1,r);
}
void modify(int l,int r,int x,int &y,int pos,int val){
y=++sz;
if(l==r){v[y]=val;deep[y]=deep[x];return;}
ls[y]=ls[x];rs[y]=rs[x];
int mid=(l+r)>>1;
if(pos<=mid)
modify(l,mid,ls[x],ls[y],pos,val);
else modify(mid+1,r,rs[x],rs[y],pos,val);
}
int query(int k,int l,int r,int pos){
if(l==r)return k;
int mid=(l+r)>>1;
if(pos<=mid)return query(ls[k],l,mid,pos);
else return query(rs[k],mid+1,r,pos);
}
void add(int k,int l,int r,int pos){
if(l==r){deep[k]++;return;}
int mid=(l+r)>>1;
if(pos<=mid)add(ls[k],l,mid,pos);
else add(rs[k],mid+1,r,pos);
}
int find(int k,int x){
int p=query(k,1,n,x);
return x==v[p]?p:find(k,v[p]);
}
int main(){
int f,k,a,b;
cin>>n>>m;
build(root[0],1,n);
for(register int i=1;i<=m;i++){
cin>>f;
if(f==1){
root[i]=root[i-1];
cin>>a>>b;
int p=find(root[i],a),q=find(root[i],b);
if(v[p]==v[q])continue;
if(deep[p]>deep[q])swap(p,q);
modify(1,n,root[i-1],root[i],v[p],v[q]);
if(deep[p]==deep[q])add(root[i],1,n,v[q]);
}else if(f==2){
cin>>k;root[i]=root[k];
}else if(f==3){
root[i]=root[i-1];
cin>>a>>b;
int p=find(root[i],a),q=find(root[i],b);
if(v[p]==v[q])cout<<1<<endl;
else cout<<0<<endl;
}
}
return 0;
}

又是熟悉的线段树(貌似从主席树开始就阴魂不散了),复杂度因线段树而多加了一个log,但是以数据结构的标准数据范围来说,这点复杂度仍然是极好的,加上读入输出优化后可以从300ms->40ms 亲自手测。

rope大发好!!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include<cstdio>
#include<algorithm>
#include<ext/rope>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
using namespace __gnu_cxx;
const int maxn=20000+10;
rope<int> *b[maxn];
int a[maxn];
int i,j,k,l,t,n,m,ans;
bool flag;
int getfa(int id,int x){
if (b[id]->at(x)==0) return x;
b[id]->replace(x,getfa(id,b[id]->at(x)));
return b[id]->at(x);
}
int main(){
flag=0;
scanf("%d%d",&n,&m);
fo(i,1,n) a[i]=0;
b[0]=new rope<int> (a,a+n+1);
fo(i,1,m){
b[i]=new rope<int> (*b[i-1]);
scanf("%d",&t);
if (t==1){
scanf("%d%d",&j,&k);
if (flag) j^=ans,k^=ans;
j=getfa(i,j);
k=getfa(i,k);
if (j!=k) b[i]->replace(k,j);
}
else if (t==2){
scanf("%d",&k);
if (flag) k^=ans;
b[i]=b[k];
}
else{
scanf("%d%d",&j,&k);
if (flag) j^=ans,k^=ans;
j=getfa(i,j);
k=getfa(i,k);
if (j!=k) ans=0;else ans=1;
printf("%d\n",ans);
}
}
}

由于这道题数据太水(zky大大别打我),所以还有一个加强连加强版。

BZOJ 3674

题目连接 什么强制在线。。。这种管用的手段。。除了xor就没有一点新意吗?(作死吐槽) 回归主题,对于上一篇来说,反正都是非离线做法,转个在线水水水!!!! 其实用类似于主席树的方法应该可以硬钢过这道题吧 代码就用上面的那一份改改就好了,这里就不发出来了。

END

到这里,可持久化并查集的三部曲就结束了,不知道大家发没发现,这三篇都有一个共同特点?

图片都是博丽灵梦!!!

click it and link me

Launch CodeCogs Equation Editor