【BZOJ-3673&3674】可持久化并查集 可持久化线段树 + 并查集

3673: 可持久化并查集 by zky

Time Limit: 5 Sec  Memory Limit: 128 MB
Submit: 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

5 6
1 1 2
3 1 2
2 0
3 1 2
2 1
3 1 2

Sample Output

1
0
1

HINT

Source

3674: 可持久化并查集加强版

Time Limit: 15 Sec  Memory Limit: 256 MB
Submit: 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

5 6
1 1 2
3 1 2
2 1
3 0 3
2 1
3 1 2

Sample Output

1
0
1

HINT

Source

出题人大SB++

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分钟才看出来...

原文地址:https://www.cnblogs.com/DaD3zZ-Beyonder/p/6180666.html