3673: 可持久化并查集 by zky
Time Limit: 5 Sec Memory Limit: 128 MBSubmit: 1878 Solved: 846
[Submit][Status][Discuss]
Description
n个集合 m个操作
操作:
1 a b 合并a,b所在集合
2 k 回到第k次操作之后的状态(查询算作操作)
3 a b 询问a,b是否属于同一集合,是则输出1否则输出0
0<n,m<=2*10^4
Input
Output
Sample Input
1 1 2
3 1 2
2 0
3 1 2
2 1
3 1 2
Sample Output
0
1
HINT
Source
3674: 可持久化并查集加强版
Time Limit: 15 Sec Memory Limit: 256 MBSubmit: 2367 Solved: 886
[Submit][Status][Discuss]
Description
Description:
自从zkysb出了可持久化并查集后……
hzwer:乱写能AC,暴力踩标程
KuribohG:我不路径压缩就过了!
ndsf:暴力就可以轻松虐!
zky:……
n个集合 m个操作
操作:
1 a b 合并a,b所在集合
2 k 回到第k次操作之后的状态(查询算作操作)
3 a b 询问a,b是否属于同一集合,是则输出1否则输出0
请注意本题采用强制在线,所给的a,b,k均经过加密,加密方法为x = x xor lastans,lastans的初始值为0
0<n,m<=2*10^5
Input
Output
Sample Input
1 1 2
3 1 2
2 1
3 0 3
2 1
3 1 2
Sample Output
0
1
HINT
Source
Solution
先说离线的一种思路,以前yveh做Codeforces的时候告诉我一道题,与之类似,存在一个状态退回到第多少步,当时是需要线段树维护一个东西,所以可以形成一个树形结构,然后dfs并回溯离线做。
但是这里并不能那么搞,因为并查集不支持删除所以没法回溯,所以只能考虑令并查集的 代表元素数组可持久化
所以本质上都得用可持久化数据结构维护代表元素数组,就无所谓离线在线了。
具体的做法就是建可持久化线段树,每次修改就在线段树上建一个新的链,叶子节点维护的是这个点的代表元素,所以方法很简单就是先初始化建出一棵新的树,然后每次新建一条链即可。
然后这里就剩下一个问题了,就是并查集$find$,为了保证复杂度,我采用了路径压缩但这里发现路径压缩会使树上操作数增多,建很多新的节点,所以常数比较大,然后看了hxy和zyf的代码,发现她们都和hzwer写的一样,采用的按秩合并,不过常数比我还大,不过显而易见 如果不加优化一定会TLE,所以二选一即可,并无太大的差距。
Code
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> using namespace std; inline 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*10+ch-'0'; ch=getchar();} return x*f; } #define MAXN 500010 int N,M,last; namespace PrTree { int sz,lson[MAXN*20],rson[MAXN*20],root[MAXN],f[MAXN*20]; inline void Insert(int l,int r,int &now,int fa,int pos,int val) { now=++sz; if (l==r) {f[now]=val; return;} int mid=(l+r)>>1; lson[now]=lson[fa],rson[now]=rson[fa]; if (pos<=mid) Insert(l,mid,lson[now],lson[fa],pos,val); else Insert(mid+1,r,rson[now],rson[fa],pos,val); } inline int Query(int l,int r,int pos,int now) { if (l==r) return f[now]; int mid=(l+r)>>1; if (pos<=mid) return Query(l,mid,pos,lson[now]); else return Query(mid+1,r,pos,rson[now]); } inline void BuildTree(int l,int r,int &now) { now=++sz; if (l==r) {f[now]=l; return;} int mid=(l+r)>>1; BuildTree(l,mid,lson[now]); BuildTree(mid+1,r,rson[now]); } }using namespace PrTree; inline int F(int x,int &rt) {int fa; if ((fa=Query(1,N,x,rt))==x) return x; else return Insert(1,N,rt,rt,x,fa=F(fa,rt)),fa;} inline void Merge(int x,int y,int &rt) {int fx=F(x,rt),fy=F(y,rt); if (fx!=fy) Insert(1,N,rt,rt,fx,fy);} int main() { N=read(),M=read(); BuildTree(1,N,root[0]); for (int i=1; i<=M; i++) { root[i]=root[i-1]; int opt=read(),x,y; switch (opt) { case 1: x=read(),y=read(); x^=last,y^=last; Merge(x,y,root[i]); break; case 2: x=read(); x^=last; root[i]=root[x]; break; case 3: x=read(),y=read(); x^=last,y^=last; printf("%d ",last=(F(x,root[i])==F(y,root[i]))); break; } } return 0; }
总体来说写起来还是蛮好写的,就是自己开始手误,在可持久化线段树的时候 左右都下放的lson,然后瞪着屏幕20分钟才看出来...