可撤销并查集
之前没有写过可撤销并查集,这里整理一下.
普通并查集是不支持撤销/断边操作的.
但是如果加边顺序是 , 断边顺序是 的话是可以维护的.
我们只需要用一个启发式合并的并查集加上栈来存储合并信息即可.
这样做可行,是因为始终是向上合并,并且删除时是从上向下删除的.
具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | 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; } } }; |
在撤销的时候删除 对 的影响就好了,这一般是较好维护的.
1.二分图 /【模板】线段树分治
来源:luogu5787
在这道题中,如果说没有删边操作则可以直接利用加权并查集维护奇偶性.
然后有删边时间的话就利用线段树分治存边,利用加权并查集在线段树上合并和撤销即可.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 | #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; } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY