【学习笔记】(15) Kruskal 重构树
前置知识:kruskal 求最小生成树,倍增。
1. 算法简介
以下所有讨论基于 最小生成树。
在 Kruskal 求最小生成树的过程:将所有边按照边权排序,若当前边
如果能用某种结构描述每条边被连接的先后顺序,因为越往后加入的边权越大,就可以快速刻画边权有限制时整张图的连通情况。
Kruskal 重构树诞生了。它在 kruskal 基础上进行了一些改进:连接
通常设
若原图
void merge(int u, int v, int w) { if((u = find(u)) == (v = find(v))) return; node++, fa[u] = fa[v] = fa[node] = node, val[node] = w; add(node, u), add(node, v); }
这是原图
这是他的 Kruskal 重构树
2.性质与应用
下文称 原节点 为原图节点,共
Kruskal 重构树
是一棵二叉树,由构建方法可知。对于部分题目,特殊重构树建法可有效减小常数,详见 Part 1.3 点权多叉重构树。 的所有节点是 的 叶子。因此原节点和重构树叶子节点本质相同。- 对于任意新节点
及其祖先 。
上述性质均可以由上图直观得知。
性质 3 非常重要,它是 Kruskal 重构树的核心:原节点
相当于从原节点
综上,我们可以总结出一个常用套路:当题目限制形如 “只经过权值不大于某个值的点或边” 时,从 Kruskal 重构树角度入手。部分题目也可以使用可持久化并查集,因为它同样能够刻画存在边权限制时图的连通情况:实现 Kruskal 最小生成树的过程中将并查集可持久化。相较于 Kruskal 重构树,可持久化并查集在时空复杂度上更劣,不建议使用。
3.点权多叉重构树
Kruskal 重构树不仅适用于限制边权的题目,也可以处理限制点权的情况。
方法一:
为原图每条边巧妙赋值,将点权转化为边权。若限制经过的点权最大值,因为走一条边 (u,v)
需满足
方法二:
实际上我们几乎用不到
不妨设题目限制点权最大值。将节点按权值从小到大排序,按序遍历每个点
及其所有出边
上述做法与一般 Kruskal 重构树几乎等价:普通重构树中点权相同的虚点 仅有深度最小的有用;按权值从小到大枚举节点相当于对所有边排序,因为边权即
推荐遇到点权重构树时尽量使用方法二,建出多叉重构树,其写法见例题 I。但读者应时刻记住 可以保证重构树是二叉树。
例题
Ⅰ. P4899 [IOI2018] werewolf 狼人
是否存在一条
由于给定了路径上点权上下界,很容易想 Kruskal 重构树。
建立两棵重构树。
在
可以使用主席树,我这里用的是 BIT。
点击查看代码
#include<bits/stdc++.h> using namespace std; const int N = 2e5 + 67; int read(){ int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-') f = -f; ch = getchar();} while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();} return x * f; } int n, m, q, lg; struct linklist{ int tot, Head[N], Next[N << 2], to[N << 2]; linklist() {tot = 0, memset(Head, 0, sizeof(Head));} void add(int u, int v){ to[++tot] = v, Next[tot] = Head[u], Head[u] = tot; } }E, ins, del; struct Tree{ int fa[N]; int find(int x){return fa[x] == x ? x : fa[x] = find(fa[x]);} void init(){for(int i = 1; i <= n; ++i) fa[i] = i;} linklist G; int f[N][20], sz[N], dfn[N], cnt; void merge(int u, int v){ if((v = find(v)) == u) return ; fa[v] = f[v][0] = u, G.add(u, v); } void dfs(int x){ dfn[x] = ++cnt, sz[x] = 1; for(int i = G.Head[x]; i; i = G.Next[i]) dfs(G.to[i]), sz[x] += sz[G.to[i]]; } void Build(int x){ dfs(x); for(int i = 1; i <= lg; ++i) for(int j = 1; j <= n; ++j) f[j][i] = f[f[j][i - 1]][i - 1]; } int anc(int u, int lim, bool t){ //寻找满足限制的最浅的祖先 for(int i = lg; ~i; --i) if(f[u][i] && (t ? f[u][i] >= lim : f[u][i] <= lim)) u = f[u][i]; return u; } }L, R; struct BIT{ int c[N]; void add(int x){for(; x <= n; x += x & -x) ++c[x];} int ask(int x){ int res = 0; for(; x; x -= x & -x) res += c[x]; return res; } int query(int l, int r){return ask(r) - ask(l - 1);} }tr; int l[N], r[N], ans[N], id[N]; int main(){ n = read(), m = read(), q = read(), lg = log2(n); for(int i = 1; i <= m; ++i){ int u = read() + 1, v = read() + 1; E.add(u, v), E.add(v, u); } L.init(), R.init(); for(int i = 1; i <= n; ++i) for(int j = E.Head[i]; j; j = E.Next[j]) if(i > E.to[j]) L.merge(i, E.to[j]); for(int i = n; i; --i) for(int j = E.Head[i]; j; j = E.Next[j]) if(i < E.to[j]) R.merge(i, E.to[j]); L.Build(n), R.Build(1); for(int i = 1; i <= n; ++i) id[L.dfn[i]] = R.dfn[i]; for(int i = 1; i <= q; ++i){ int s = read() + 1, e = read() + 1, ql = read() + 1, qr = read() + 1; s = R.anc(s, ql, 1); e = L.anc(e, qr, 0); del.add(L.dfn[e] - 1, i); // 不在范围内的点为 [1, L.dfn[e] - 1] ins.add(L.dfn[e] + L.sz[e] - 1, i); l[i] = R.dfn[s], r[i] = R.dfn[s] + R.sz[s] - 1; } for(int i = 1; i <= n; ++i){ //判断有没有交集 tr.add(id[i]); for(int j = ins.Head[i]; j; j = ins.Next[j]){ int y = ins.to[j]; ans[y] += tr.query(l[y], r[y]); } for(int j = del.Head[i]; j; j = del.Next[j]){ int y = del.to[j]; ans[y] -= tr.query(l[y], r[y]); //减去不在范围内的点 } } for(int i = 1; i <= q; ++i) printf("%d\n", (ans[i] > 0)); return 0; }
Ⅱ. P7834 [ONTAK2010] Peaks 加强版
看到只经过权值
静态区间第
点击查看代码
#include<bits/stdc++.h> using namespace std; const int N = 5e5 + 67; int read(){ int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-') f = -f; ch = getchar();} while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();} return x * f; } int n, m, q, lg, ans; int tot, Head[N], Next[N << 1], to[N << 1]; void add(int u, int v){ to[++tot] = v, Next[tot] = Head[u], Head[u] = tot; } struct Edge{ int u, v, w; bool operator < (const Edge &I) const{return w < I.w;} }A[N]; struct Tree{ int fa[N], val[N], id[N], f[N][20], sz[N], dfn[N], st[N], ed[N], cnt, node; int find(int x){return fa[x] == x ? x : fa[x] = find(fa[x]);} void init(){for(int i = 1; i <= n; ++i) fa[i] = i; node = n;} void merge(int u, int v, int w){ if((v = find(v)) == (u = find(u))) return ; ++node, fa[u] = fa[v] = fa[node] = node, val[node] = w; add(node, u), add(node, v); } void dfs(int x){ dfn[x] = ++cnt, id[cnt] = x, st[x] = cnt; for(int i = Head[x]; i; i = Next[i]) dfs(to[i]), sz[x] += sz[to[i]], f[to[i]][0] = x; if(!sz[x]) sz[x] = 1; ed[x] = cnt; } void Build(int x){ dfs(x); for(int i = 1; i <= lg; ++i) for(int j = 1; j <= node; ++j) f[j][i] = f[f[j][i - 1]][i - 1]; } int anc(int u, int lim){ for(int i = lg; ~i; --i) if(f[u][i] && val[f[u][i]] <= lim) u = f[u][i]; return u; } }T; struct SegmentTree{ int lc[N << 5], rc[N << 5], val[N << 5], cnt; int modify(int v, int l, int r, int w){ int u = ++cnt; lc[u] = lc[v], rc[u] = rc[v], val[u] = val[v] + 1; if(l == r) return u; int mid = (l + r) >> 1; if(w <= mid) lc[u] = modify(lc[v], l, mid, w); else rc[u] = modify(rc[v], mid + 1, r, w); return u; } int query(int u, int v, int l, int r, int k){ if(l == r) return l; int x = val[rc[u]] - val[rc[v]]; int mid = (l + r) >> 1; if(k > x) return query(lc[u], lc[v], l, mid, k - x); else return query(rc[u], rc[v], mid + 1, r, k); } }tr; int b[N], a[N], rt[N]; int main(){ n = read(), m = read(), q = read(), lg = log2(n) + 1; for(int i = 1; i <= n; ++i) b[i] = a[i] = read(); sort(b + 1, b + 1 + n); int nn = unique(b + 1, b + 1 + n) - b - 1; for(int i = 1; i <= n; ++i) a[i] = lower_bound(b + 1, b + 1 + nn, a[i]) - b; for(int i = 1; i <= m; ++i){ int u = read(), v = read(), w = read(); A[i] = (Edge){u, v, w}; } sort(A + 1, A + 1 + m); T.init(); for(int i = 1; i <= m; ++i) T.merge(A[i].u, A[i].v, A[i].w); T.Build(T.node); for(int i = 1; i <= T.node; ++i){ rt[i] = rt[i - 1]; if(T.id[i] <= n) rt[i] = tr.modify(rt[i - 1], 1, nn, a[T.id[i]]); } while(q--){ int u = read(), x = read(), k = read(); int v = T.anc(u, x); if(T.sz[v] < k){ puts("-1");continue; } printf("%d\n", b[tr.query(rt[T.ed[v]], rt[T.st[v] - 1], 1, nn, k)]); } return 0; }
Ⅲ. P4768 [NOI2018] 归程
我们需要现预处理出每个点到 1 号点的距离。
建立边权从大到小的 Kruskal 重构树。
找到车子能开过到的点, 显然都在可以转成一段连续的区间,用线段树来区间查询即可。
由于多组数据,一定要清空数组,不然直接 100 -> 5。(样例里居然没有多组数据)。
点击查看代码
#include<bits/stdc++.h> #define ls u << 1 #define rs u << 1 | 1 using namespace std; const int N = 5e5 + 67; int read(){ int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-') f = -f; ch = getchar();} while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();} return x * f; } int n, m, q, lg, ans, T, k, s; int dis[N], vis[N]; int tot, Head[N], Next[N << 1], to[N << 1], edge[N << 1]; void add(int u, int v, int w){ to[++tot] = v, Next[tot] = Head[u], Head[u] = tot, edge[tot] = w; } struct Edge{ int u, v, w; bool operator < (const Edge &I) const{return w > I.w;} }A[N]; struct Tree{ int tot, Head[N], Next[N << 1], to[N << 1]; void add(int u, int v){ to[++tot] = v, Next[tot] = Head[u], Head[u] = tot; } int fa[N], val[N], id[N], f[N][30], sz[N], dfn[N], st[N], ed[N], cnt, node; int find(int x){return fa[x] == x ? x : fa[x] = find(fa[x]);} void init(){ for(int i = 1; i <= n; ++i) fa[i] = i; node = n, cnt = 0, tot = 0; memset(Head, 0, sizeof(Head)); } void merge(int u, int v, int w){ if((v = find(v)) == (u = find(u))) return ; ++node, fa[u] = fa[v] = fa[node] = node, val[node] = w; add(node, u), add(node, v); } void dfs(int x){ dfn[x] = ++cnt, id[cnt] = x, st[x] = cnt, sz[x] = 0; for(int i = Head[x]; i; i = Next[i]) dfs(to[i]), sz[x] += sz[to[i]], f[to[i]][0] = x; if(!sz[x]) sz[x] = 1; ed[x] = cnt; } void Build(int x){ dfs(x); for(int i = 1; i <= lg; ++i) for(int j = 1; j <= node; ++j) f[j][i] = f[f[j][i - 1]][i - 1]; } int anc(int u, int lim){ for(int i = lg; ~i; --i) if(f[u][i] && val[f[u][i]] >= lim) u = f[u][i]; return u; } }Kr; struct SegmentTree{ int minn[N << 2], cnt; void PushUp(int u){ minn[u] = min(minn[ls], minn[rs]); } void Build(int u, int l, int r){ if(l >= r) return minn[u] = dis[Kr.id[l]], void(); int mid = (l + r) >> 1; Build(ls, l, mid), Build(rs, mid + 1, r); PushUp(u); } int Query(int u, int l, int r, int L, int R){ if(L <= l && r <= R) return minn[u]; int mid = (l + r) >> 1, ans = 0x3f3f3f3f; if(L <= mid) ans = min(ans, Query(ls, l, mid, L, R)); if(R > mid) ans = min(ans, Query(rs, mid + 1, r, L, R)); return ans; } }tr; struct DIJ{ int dis, s; bool operator < (const DIJ &A) const{return A.dis < dis;} }; void dijkstra(int s){ priority_queue<DIJ> q; memset(dis, 0x3f, sizeof(dis)); memset(vis, 0, sizeof(vis)); dis[s] = 0, q.push((DIJ){0, s}); while(!q.empty()){ int x = q.top().s; q.pop(); if(vis[x]) continue; vis[x] = 1; for(int i = Head[x]; i; i = Next[i]){ int y = to[i]; if(dis[y] > dis[x] + edge[i]){ dis[y] = dis[x] + edge[i]; q.push((DIJ){dis[y], y}); } } } } int b[N], a[N]; int main(){ // freopen("1.in", "r", stdin); // freopen("return.out", "w", stdout); T = read(); while(T--){ n = read(), m = read(), lg = log2(n) + 1, ans = 0; tot = 0; memset(Head, 0, sizeof(Head)); for(int i = 1; i <= m; ++i){ int u = read(), v = read(), l = read(), w = read(); A[i] = (Edge){u, v, w}, add(u, v, l), add(v, u, l); } dijkstra(1); sort(A + 1, A + 1 + m); Kr.init(); for(int i = 1; i <= m; ++i) Kr.merge(A[i].u, A[i].v, A[i].w); Kr.Build(Kr.node), tr.Build(1, 1, Kr.node); q = read(), k = read(), s = read(); while(q--){ int u = (read() + k * ans - 1) % n + 1, p = (read() + k * ans) % (s + 1); int v = Kr.anc(u, p + 1); printf("%d\n", ans = tr.Query(1, 1, Kr.node, Kr.st[v], Kr.ed[v])); } } return 0; }
Ⅳ.CF1628E Groceries in Meteor Town
首先看到到两个点之间的简单路径中经过的最长的边,就去想是否可以用 边权从小到大的 Kruskal 重构树转化成两个点的 lca 的权值。
于是查询操作就转化成了在重构树上,
再用一下 CF1062E Company 这题的经典结论:一个点集的 lca 为这个点集中
点击查看代码
#include<bits/stdc++.h> #define ls u << 1 #define rs u << 1 | 1 using namespace std; const int N = 6e5 + 67; int read(){ int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-') f = -f; ch = getchar();} while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();} return x * f; } int n, q, lg; struct Edge{ int u, v, w; bool operator < (const Edge &A) const{return w < A.w;} }A[N]; int fa[N], f[N][20], val[N], dfn[N], d[N], id[N], node, cnt; int tot, Head[N], to[N << 1], Next[N << 1]; struct Krusal{ void init(){for(int i = 1; i <= n; ++i) fa[i] = i; node = n;} int find(int x){return x == fa[x] ? x : fa[x] = find(fa[x]);} void add(int u, int v){ to[++tot] = v, Next[tot] = Head[u], Head[u] = tot; } void merge(int u, int v, int w){ if((u = find(u)) == (v = find(v))) return ; ++node, fa[u] = fa[v] = fa[node] = node, val[node] = w; add(node, u), add(node, v); } void dfs(int x){ dfn[x] = ++cnt, id[cnt] = x; for(int i = Head[x]; i; i = Next[i]) d[to[i]] = d[x] + 1, dfs(to[i]), f[to[i]][0] = x; } void build(int x){ d[x] = 1, dfs(x); for(int i = 1; i <= lg; ++i) for(int j = 1; j <= node; ++j) f[j][i] = f[f[j][i - 1]][i - 1]; } }Kr; int minn[N << 2], maxn[N << 2], mn[N << 2], mx[N << 2], lazy[N << 2]; void build(int u, int l, int r){ mn[u] = 1e9, mx[u] = 0, lazy[u] = -1; if(l == r) return maxn[u] = minn[u] = dfn[l], void(); int mid = (l + r) >> 1; build(ls, l, mid), build(rs, mid + 1, r); maxn[u] = max(maxn[ls], maxn[rs]), minn[u] = min(minn[ls], minn[rs]); } void update(int u, int t){ t == 1 ? (mx[u] = maxn[u], mn[u] = minn[u]) : (mx[u] = 0, mn[u] = 1e9); } void pushdown(int u){ lazy[ls] = lazy[rs] = lazy[u]; update(ls, lazy[u]), update(rs, lazy[u]); lazy[u] = -1; } void modify(int u, int l, int r, int L, int R, int t){ if(L <= l && r <= R) return update(u, t), lazy[u] = t, void(); int mid = (l + r) >> 1; if(~lazy[u]) pushdown(u); if(L <= mid) modify(ls, l, mid, L, R, t); if(R > mid) modify(rs, mid + 1, r, L, R, t); mn[u] = min(mn[ls], mn[rs]), mx[u] = max(mx[ls], mx[rs]); } int lca(int x, int y){ if(d[x] < d[y]) swap(x, y); for(int i = lg; ~i; --i) if(d[f[x][i]] >= d[y]) x = f[x][i]; if(x == y) return x; for(int i = lg; ~i; --i) if(f[x][i] != f[y][i]) x = f[x][i], y = f[y][i]; return f[x][0]; } int main(){ n = read(), q = read(), lg = log2(n) + 1; for(int i = 1; i < n; ++i){ int u = read(), v = read(), w = read(); A[i] = (Edge){u, v, w}; } sort(A + 1, A + n); Kr.init(); for(int i = 1; i < n; ++i) Kr.merge(A[i].u, A[i].v, A[i].w); Kr.build(node); build(1, 1, n); while(q--){ int opt = read(), l, r, x; if(opt <= 2){ l = read(), r = read(); modify(1, 1, n, l, r, opt == 1); }else{ x = read(); l = mn[1], r = mx[1]; if(l == 1e9 || (l == dfn[x] && l == r)) puts("-1"); else printf("%d\n", val[lca(id[l], lca(x, id[r]))]); } } return 0; }
完结撒花~
本文作者:南风未起
本文链接:https://www.cnblogs.com/jiangchen4122/p/17419355.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步