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

http://www.lydsy.com/JudgeOnline/problem.php?id=3674

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

Time Limit: 15 Sec  Memory Limit: 256 MB
Submit: 3225  Solved: 1192
[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
 
题意很简单,就是要你写个可持久化并查集,建议做这题之前先去看我的上一篇博客——可持久化线段树,以及你必须会普通版的并查集。
如何构造一个可持久化并查集呢?我们知道并查集是由数组实现的,所以只要能写出可持久化数组,自然就能写出可持久化并查集了。查了一些资料,可持久化数组似乎就是可持久化线段树(这个我也不太确定),但是我们还是可以用线段树代替一下数组实现并查集,我们只需要线段树的叶节点去合并就可以了,实际上线段树上面的区间都是没什么用的,但是又不能删掉,因为我们还要维护root[i]。同时要注意并查集合并的时候不要路径压缩,因为压缩后会影响太多点,返回历史版本会很难,所以我们选择人畜无害的按秩合并,同样可以快到飞起。具体实现看代码吧。可持久化并查集可以用pb_ds库的rope,现成模板,我也贴一份模板在下面了,不过这个跑起来的时间是自己写的版本的两倍。(PS:其实是可以进行路径压缩的,但是空间要稍微开大一点)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
#include <queue>
#include <string>
#include <stack>
#include <map>
#include <set>
#include <bitset>
#define X first
#define Y second
#define clr(u,v); memset(u,v,sizeof(u));
#define in() freopen("data","r",stdin);
#define out() freopen("ans","w",stdout);
#define Clear(Q); while (!Q.empty()) Q.pop();
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 2e5 + 10;
const int INF = 0x3f3f3f3f;
struct Tree
{
    int l, r, fa;
} T[maxn<<5];
int root[maxn], cnt = 0;
int deep[maxn];//并查集深度
int n, m;

void build(int l, int r, int &x)//先建树,进行初始化,如果有多case记得cnt初始化为0
{
    x = ++cnt;
    if (l == r)
    {
        T[x].fa = l;
        deep[l] = 1;
        return ;
    }
    int mid = (l + r) >> 1;
    build(l, mid, T[x].l);
    build(mid + 1, r, T[x].r);
}

int query(int l, int r, int rt, int pos)//查询pos在线段树中的位置
{
    if (l == r) return rt;
    int mid = (l + r) >> 1;
    if (mid >= pos) return query(l, mid, T[rt].l, pos);
    else return query(mid + 1, r, T[rt].r, pos);
}

int find(int x, int cur)//并查集的找祖先
{
    int pos = query(1, n, root[cur], x);
    if (T[pos].fa != x)
        return find(T[pos].fa, cur);
    return T[pos].fa;
}

void update(int l, int r, int &x, int y, int pos, int fa)
{
    T[++cnt] = T[y];//继承上一个版本
    x = cnt;//与父节点连接
    if (l == r)
    {
        T[x].fa = fa;//更新fa值
        return ;
    }
    int mid = (l + r) >> 1;
    if (mid >= pos) update(l, mid, T[x].l, T[y].l, pos, fa);
    else update(mid + 1, r, T[x].r, T[y].r, pos, fa);
}

void mix(int x, int y, int cur)
{
    int fx = find(x, cur), fy = find(y, cur);
    if (deep[fx] > deep[fy]) swap(fx, fy);//按秩合并
    if (fx == fy) return ;
    deep[fy] += deep[fx];
    update(1, n, root[cur], root[cur-1], fx, fy);//在线段树fx的位置上更新为fy
}

int main()
{
#ifdef LOCAL
    in();
#endif
    scanf("%d%d", &n, &m);
    build(1, n, root[0]);
    int cur = 0, lastans = 0;
    while (m--)
    {
        int op, a, b, k;
        scanf("%d", &op);
        if (op == 1)
        {
            scanf("%d%d", &a, &b);
            a ^= lastans;
            b ^= lastans;
            root[cur+1] = root[cur];
            mix(a, b, ++cur);
        }
        else if (op == 2)
        {
            scanf("%d", &k);
            k ^= lastans;
            root[++cur] = root[k];//这里不建议改成cur = k ,因为他可能跳到前面后又跳回去,这样会覆盖掉历史版本
        }
        else
        {
            scanf("%d%d", &a, &b);
            a ^= lastans;
            b ^= lastans;
            root[cur+1] = root[cur];
            ++cur;
            lastans = (find(a, cur) == find(b, cur));
            printf("%d\n", lastans);
        }
    }
    return 0;
}

pb_ds库的rope:

#include <bits/stdc++.h>
#include <cstdio>
#include <ext/rope>
using namespace std;
using namespace __gnu_cxx;
const int maxn = 2e5 + 10;
rope <int> *f[maxn];
rope <int> :: iterator it;
int id[maxn];
int n, m, op, a, b;
int find (int &t, int x)
{
    if (f[t]->at(x) == x) return x;
    int fa = find(t, f[t]->at(x));
    if (fa == f[t]->at(x)) return fa;
    f[t]->replace(x, fa);
    return fa;
}
void mix(int &t, int x, int y)
{
    int fx = find(t, x), fy = find(t, y);
    f[t]->replace(fy, fx);
}
int lastans = 0;
int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 0; i <= n; ++i) id[i] = i;
    f[0] = new rope<int>(id, id + n + 1);
    for (int i = 1; i <= m; ++i)
    {
        f[i] = new rope <int>(*f[i-1]);
        scanf("%d", &op);
        if (op == 1)
        {
            scanf("%d%d", &a, &b);
            mix(i, a ^ lastans, b ^ lastans);
        }
        else if (op == 2)
        {
            scanf("%d", &a);
            f[i] = f[lastans^a];
        }
        else if (op == 3)
        {
            scanf("%d%d", &a, &b);
            printf("%d\n", lastans = find(i, a ^ lastans) == find(i, b ^ lastans));
        }
    }
    return 0;
}

 附上路径压缩版本,路径压缩要updata的次数更多,所以空间也会大点,实测和按秩合并速度差不多,大概是updata也花了时间

int find(int x, int cur)//并查集的找祖先
{
    int pos = query(1, n, root[cur], x);
    if (T[pos].fa != x)
    {
        int temp = find(T[pos].fa, cur);
        update(1, n, root[cur], root[cur], T[pos].fa, temp);
        return temp;
    }
    return T[pos].fa;
}

  

 

posted @ 2017-05-16 21:42  酱油党gsh  阅读(187)  评论(0编辑  收藏  举报