可撤銷並查集
阿新 • • 發佈:2021-10-31
之前沒有寫過可撤銷並查集,這裡整理一下.
普通並查集是不支援撤銷/斷邊操作的.
但是如果加邊順序是 $(1,2,3,4,5)$, 斷邊順序是 $(5,4,3,2,1)$ 的話是可以維護的.
我們只需要用一個啟發式合併的並查集加上棧來儲存合併資訊即可.
這樣做可行,是因為始終是向上合併,並且刪除時是從上向下刪除的.
具體程式碼如下:
namespace ufs { stack<int>S; int dis[N], fa[N], size[N]; void init() { for(int i=1;i<N;++i) { dis[i] = 0, fa[i] = i, size[i] = 1; } } int find(int x) { return fa[x] == x ? fa[x] : find(fa[x]); } int dist(int x) { return fa[x] == x ? dis[x] : dis[x] ^ dist(fa[x]); } void merge(int x, int y) { int dx = dist(x); int dy = dist(y); x = find(x), y = find(y); if(size[x] > size[y]) { swap(x, y); swap(dx, dy); } // size[y] > size[x] // 令 f[x] -> y S.push(x); size[y] += size[x]; fa[x] = y, dis[x] = dx ^ dy ^ 1; } void undo(int si) { // 撤銷. while(S.size() > si) { int p = S.top(); S.pop(); size[fa[p]] -= size[p]; fa[p] = p, dis[p] = 0; } } };
在撤銷的時候刪除 $\mathrm{x}$ 對 $\mathrm{fa[x]}$ 的影響就好了,這一般是較好維護的.
1.二分圖 /【模板】線段樹分治
來源:luogu5787
在這道題中,如果說沒有刪邊操作則可以直接利用加權並查集維護奇偶性.
然後有刪邊時間的話就利用線段樹分治存邊,利用加權並查集線上段樹上合併和撤銷即可.
#include <cstdio> #include <vector> #include <cstring> #include <stack> #include <algorithm> #define N 200009 #define ll long long #define pb push_back #define ls now << 1 #define rs now << 1 | 1 #define setIO(s) freopen(s".in","r",stdin) using namespace std; namespace ufs { stack<int>S; int dis[N], fa[N], size[N]; void init() { for(int i=1;i<N;++i) { dis[i] = 0, fa[i] = i, size[i] = 1; } } int find(int x) { return fa[x] == x ? fa[x] : find(fa[x]); } int dist(int x) { return fa[x] == x ? dis[x] : dis[x] ^ dist(fa[x]); } void merge(int x, int y) { int dx = dist(x); int dy = dist(y); x = find(x), y = find(y); if(size[x] > size[y]) { swap(x, y); swap(dx, dy); } // size[y] > size[x] // 令 f[x] -> y S.push(x); size[y] += size[x]; fa[x] = y, dis[x] = dx ^ dy ^ 1; } void undo(int si) { // 撤銷. while(S.size() > si) { int p = S.top(); S.pop(); size[fa[p]] -= size[p]; fa[p] = p, dis[p] = 0; } } }; int n, m, K; struct oper { int x, y; oper(int x=0,int y=0):x(x),y(y){} }; vector<oper>G[N << 2]; void update(int l, int r, int now, int L, int R, oper v) { if(l >= L && r <= R) { G[now].pb(v); return ; } int mid = (l + r) >> 1; if(L <= mid) update(l, mid, ls, L, R, v); if(R > mid) update(mid + 1, r, rs, L, R, v); } void dfs(int l, int r, int now, int flag) { int cur = ufs::S.size(); if(!flag) { // 若還沒構成二分圖,則可以加邊. for(int i = 0 ; i < G[now].size() ; ++ i) { int x = G[now][i].x; int y = G[now][i].y; int px = ufs::find(x); int py = ufs::find(y); if(px != py) { ufs::merge(x, y); } else { int dx = ufs::dist(x); int dy = ufs::dist(y); if(dx ^ dy) { continue; } else { flag = 1; break; } } } } if(l == r) { printf("%s\n", flag ? "No" : "Yes"); return ; } int mid = (l + r) >> 1; dfs(l, mid, ls, flag); dfs(mid + 1, r, rs, flag); ufs::undo(cur); } int main() { // setIO("input"); scanf("%d%d%d",&n,&m,&K); ufs::init(); for(int i = 1; i <= m ; ++ i) { int x, y, l, r; scanf("%d%d%d%d",&x, &y, &l, &r); update(0, K - 1, 1, l, r - 1, oper(x, y)); } // 處理完了, 可以遍歷了. dfs(0, K - 1, 1, 0); return 0; }