可撤销并查集

给定n个结点,q次询问,每次询问分为三类:

1 x y:可以选择将x,y两个点连通,如果已经连通则不操作。

2 :撤销上一次的操作(若全部撤销完了则不操作)。

3 x y:询问x*,*y是否连通,如果是则输出”YES”,反之输出”NO”,请注意都是大写字母,不包含引号。

struct DSU {
   std::vector<int> siz;
   std::vector<int> f;
   std::vector<std::array<int, 2>> his;

   DSU(int n) : siz(n + 1, 1), f(n + 1) {
       std::iota(f.begin(), f.end(), 0);
   }

   int find(int x) {
       while (f[x] != x) {
           x = f[x];
       }
       return x;
   }

   bool merge(int x, int y) {
       x = find(x);
       y = find(y);
       if (x == y) {
           return false;
       }
       if (siz[x] < siz[y]) {
           std::swap(x, y);
       }
       his.push_back({x, y});
       siz[x] += siz[y];
       f[y] = x;
       return true;
   }

   int time() {
       return his.size();
   }

   void revert(int tm) {
       while (his.size() > tm) {
           auto [x, y] = his.back();
           his.pop_back();
           f[y] = y;
           siz[x] -= siz[y];
       }
   }
};

void solve() {
   int n, m;
   cin >> n >> m;
   DSU dsu(n + 1);
   for (int i = 1; i <= m; i++) {
       int op;
       cin >> op;
       if (op == 1) {
           int x, y;
           cin >> x >> y;
           dsu.merge(x, y);
       } else if (op == 2)
           dsu.revert(dsu.his.size() - 1);
       else {
           int x, y;
           cin >> x >> y;
           if (dsu.find(x) == dsu.find(y))
               cout << "YES" << endl;
           else
               cout << "NO" << endl;
       }
   }
}

可持久化并查集

给定 $n$ 个集合,第 $i$ 个集合内初始状态下只有一个数,为 $i$。

有 $m$ 次操作。操作分为 $3$ 种:

  • 1 a b 合并 $a,b$ 所在集合;

  • 2 k 回到第 $k$ 次操作(执行三种操作中的任意一种都记为一次操作)之后的状态;

  • 3 a b 询问 $a,b$ 是否属于同一集合,如果是则输出 $1$,否则输出 $0$。

输入格式

第一行两个整数,$n,m$。

接下来 $m$ 行,每行先输入一个数 $opt$。若 $opt=2$ 则再输入一个整数 $k$,否则再输入两个整数 $a,b$,描述一次操作。

输出格式

对每个操作 $3$,输出一行一个整数表示答案。

Sol:离线用可撤销并查集。操作的撤销有点像dfs的状态回溯,将操作序列的编号作为树的编号,并查集的状态为树的父亲儿子关系。在dfs过程中回答询问,回溯的时候根据需要撤销操作。

实现:注意可能有无效的合并的操作,这时候对应回溯的时候不能撤销。

struct DSU {
    std::vector<int> siz;
    std::vector<int> f;
    std::vector<std::array<int, 2>> his;

    DSU(int n) : siz(n + 1, 1), f(n + 1) {
        std::iota(f.begin(), f.end(), 0);
    }

    int find(int x) {
        while (f[x] != x) {
            x = f[x];
        }
        return x;
    }

    bool merge(int x, int y) {
        x = find(x);
        y = find(y);
        if (x == y) {
            return false;
        }
        if (siz[x] < siz[y]) {
            std::swap(x, y);
        }
        his.push_back({x, y});
        siz[x] += siz[y];
        f[y] = x;
        return true;
    }

    int time() {
        return his.size();
    }

    void revert(int tm) {
        while ((int)his.size() > tm) {
            auto [x, y] = his.back();
            his.pop_back();
            f[y] = y;
            siz[x] -= siz[y];
        }
    }
    void backlast() {
        assert(his.size());
        // if (his.size() == 0)
        //     return;
        revert(his.size() - 1);
    }
};

void solve() {//可持久化并查集-离线
    int n, m;
    cin >> n >> m;
    vector<vector<int>> e(m + 1);
    DSU dsu(n);
    vector<array<int, 3>> type(m + 1);
    for (int i = 1; i <= m; i++) {
        cin >> type[i][0];
        if (type[i][0] == 1 || type[i][0] == 3) {
            e[i - 1].push_back(i);
            cin >> type[i][1] >> type[i][2];
        } else {
            int k;
            cin >> k;
            assert(k < i);
            e[k].push_back(i);
        }
    }
    vector<int> ans(m + 1);
    function<void(int)> dfs = [&](int u) {
        int tmp = dsu.time();

        for (auto v : e[u]) {
            // int tmp = dsu.time();
            auto [op, x, y] = type[v];
            bool flag = 0;
            if (op == 1)
                flag = dsu.merge(x, y);//必须合并成功才能在下面撤销
            else if (op == 3)
                ans[v] = (dsu.find(x) == dsu.find(y)) ? 1 : 0;
            dfs(v);
            // dsu.revert(tmp);
            if (op == 1 && flag)
                dsu.backlast();
        }
    };
    dfs(0);
    for (int i = 1; i <= m; i++) {
        if (type[i][0] == 3) {
            deb(i);
            cout << ans[i] << endl;
        }
    }
}