【学习笔记/习题总结】kruskal重构树
kruskal 重构树
注:默认您学会了求最小生成树的 kruskal 算法,并且知道何为最小瓶颈生成树和最小瓶颈路。
定义:
在跑 kruskal 的过程中我们会从小到大加入若干条边,我们仍然按照这个顺序加边,并且视这条边为一个节点,点权为加入边的边权。
一次加边会合并两个集合,我们分别以这两个集合的根节点为新建点的左、右儿子,然后将两个集合和新点合并为一个集合,将新建点设为根。
在进行 轮加边后,我们就得到了一棵恰有 个叶子的二叉树,同时每个非叶子节点恰有两个儿子。这棵树就是 kruskal 重构树。
举个例子,这张图:
建出 kruskal 重构树是:
点上括号里的数表示点权。
实现:
其实和 kruskal 求最小生成树的实现一样,对所有边排序,依次考虑这条边可不可以被加入,通过并查集维护两点之间的联通性,没了。
struct Union_Set{ int fa[MAXN]; void init(int n){ for(register int i = 1; i <= n; i++) fa[i] = i; } int Find(int x){ return x == fa[x] ? x : fa[x] = Find(fa[x]); } }U; //并查集 void Kruskal(){ U.init(n << 1); //初始化并查集 int tot = 0; sum = n; //tot 是加进去了多少边,sum 是重构树上点的数量 sort(line + 1, line + 1 + m, cmp); for(register int i = 1; i <= m; i++){ int u = line[i].from, v = line[i].to; int fa_u = U.Find(u), fa_v = U.Find(v); if(fa_u != fa_v){ ++tot; val[++sum] = line[i].dis; Add(sum, fa_u), Add(fa_u, sum); Add(sum, fa_v), Add(fa_v, sum); } if(tot == n - 1) break; //加进 n - 1 条边一定能构成树 } }
性质:
由于 kruskal 重构树是一棵二叉树,并且是依次加边,所以它有一些美妙的性质:
如果我们将叶子节点的权值视为 ,则整棵树满足堆结构。所以任意两点间路径的最小权值的最大值即为它们 的点权。
也就是说,到点 的简单路径上最大边权的最小值 的所有点 均在 kruskal 重构树上的某一颗子树内,且恰为这棵子树中的叶子结点。
题:
P4768 [NOI2018] 归程
个节点 条边的无向图,用 , 表示长度,海拔。
有 次询问,给定起点 ,水位线 ,只能经过海拔不低于 的边,求能到达的点中距离 号节点最近的距离。
首先跑一遍单源最短路,预处理出每个点到 号节点的最短路径,注意到这是归程,所以 spfa
会被卡,所以要用 dijstra
。然后建出关于海拔 的 kruskal 重构树,要关于海拔 降序排序使得 kruskal 重构树满足大根堆性质,那么能到达的点是重构树中一个子树的所有叶子节点。然后我们去找节点 到根的路径上最浅的海拔不低于 的点,这一步可以倍增处理。然后以该节点为根的子树中的叶子结点到 节点的最短路径就是答案,直接 dfs
时预处理就行,不要像写这篇博的傻逼一样把树拍扁了再在 dfs
序上线段树处理最小值。
Code
#include<queue> #include<vector> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int MAXN = 2e5 + 10, MAXM = 4e5 + 10, SIZE = 25; const int INF = 2147483647; int t, n, m, q, k, s, cnt, sum, num, last; int head[MAXN << 1], dis[MAXN], data[MAXN]; int val[MAXN << 1], lpos[MAXN << 1], rpos[MAXN << 1]; int fa[MAXN << 1][SIZE]; bool vis[MAXN]; struct Line{ int from, to, dis; }line[MAXM]; inline bool cmp(const Line &a, const Line &b){ return a.dis > b.dis; } struct Edge{ int to, next, dis; }e[MAXM << 1]; inline void Add(int u, int v, int w){ e[++cnt].to = v; e[cnt].dis = w; e[cnt].next = head[u]; head[u] = cnt; } struct Road{ int dis, pos; bool operator > (const Road &a) const{ return dis > a.dis; } }; inline int read(){ int x = 0, f = 1; char c = getchar(); while(c < '0' || c > '9'){ if(c == '-') f = -1; c = getchar(); } while(c >= '0' && c <= '9'){ x = (x << 1) + (x << 3) + (c ^ 48); c = getchar(); } return x * f; } void Dijkstra(int u){ priority_queue< Road, vector<Road>, greater<Road> > q; memset(vis, 0, sizeof(vis)); memset(dis, 0x3f, sizeof(dis)); dis[u] = 0; q.push((Road){0, u}); while(!q.empty()){ Road t = q.top(); q.pop(); if(vis[t.pos]) continue; vis[t.pos] = true; for(register int i = head[t.pos]; i; i = e[i].next){ int v = e[i].to; if(dis[v] > dis[t.pos] + e[i].dis){ dis[v] = dis[t.pos] + e[i].dis; if(!vis[v]) q.push((Road){dis[v], v}); } } } } struct Union_Set{ int fa[MAXN << 1]; void init(int n){ for(register int i = 1; i <= n; i++) fa[i] = i; } int Find(int x){ return x == fa[x] ? x : fa[x] = Find(fa[x]); } }U; void Kruscal(){ int tot = 0; cnt = 0; memset(head, 0, sizeof(head)); U.init(n << 1); sort(line + 1, line + 1 + m, cmp); for(register int i = 1; i <= m; i++){ int u = line[i].from, v = line[i].to; int fa_u = U.Find(u), fa_v = U.Find(v); if(fa_u != fa_v){ ++tot; val[++sum] = line[i].dis; Add(fa_u, sum, 0), Add(sum, fa_u, 0); Add(fa_v, sum, 0), Add(sum, fa_v, 0); U.fa[fa_u] = U.fa[fa_v] = sum; } if(tot == n - 1) break; } } void dfs(int rt, int father){ lpos[rt] = num + 1; fa[rt][0] = father; for(register int i = 1; fa[rt][i - 1]; i++) fa[rt][i] = fa[fa[rt][i - 1]][i - 1]; int son = 0; for(register int i = head[rt]; i; i = e[i].next){ int v = e[i].to; if(v == father) continue; ++son, dfs(v, rt); } if(!son) data[++num] = dis[rt]; rpos[rt] = num; } struct Segment_Tree{ struct Tree{ int l, r; int min; }tr[MAXN << 2]; inline int lson(int rt){ return rt << 1; } inline int rson(int rt){ return rt << 1 | 1; } void Pushup(int rt){ tr[rt].min = min(tr[lson(rt)].min, tr[rson(rt)].min); } void Build(int rt, int l, int r){ tr[rt].l = l, tr[rt].r = r; if(l == r){ tr[rt].min = data[l]; return; } int mid = (l + r) >> 1; Build(lson(rt), l, mid); Build(rson(rt), mid + 1, r); Pushup(rt); } int Query_Min(int rt, int l, int r){ if(tr[rt].l >= l && tr[rt].r <= r) return tr[rt].min; int ans = INF; int mid = (tr[rt].l + tr[rt].r) >> 1; if(l <= mid) ans = min(ans, Query_Min(lson(rt), l, r)); if(r > mid) ans = min(ans, Query_Min(rson(rt), l, r)); return ans; } }S; int Query(int v, int p){ for(register int i = 24; i >= 0; i--) if(fa[v][i] && val[fa[v][i]] > p) v = fa[v][i]; return S.Query_Min(1, lpos[v], rpos[v]); } void Clear(){ cnt = num = sum = last = 0; memset(fa, 0, sizeof(fa)); memset(head, 0, sizeof(head)); } int main(){ t = read(); while(t--){ Clear(); n = read(), m = read(), sum = n; for(register int i = 1; i <= m; i++){ int u, v, w, p; u = read(), v = read(), w = read(), p = read(); Add(u, v, w), Add(v, u, w); line[i] = (Line){u, v, p}; } Dijkstra(1); Kruscal(); dfs(sum, 0); S.Build(1, 1, n); q = read(), k = read(), s = read(); for(register int i = 1; i <= q; i++){ int v, p; v = (read() + k * last - 1) % n + 1, p = (read() + k * last) % (s + 1); last = Query(v, p); printf("%d\n", last); } } return 0; }
P1967 [NOIP2013] 货车运输
给定一个 个点 条边的无向图,每条边有一个权值, 次询问,求 , 两点路径上最大的权值的最小值。
权值的限制可以想到 kruskal 重构树,求最大权值的最小值显然要按照最大生成树建重构树, 的权值即为 到 路径上的最大权值的最小值。
纯口胡,没写过,写的两个 的树剖。
P3280 [SCOI2013] 摩托车交易
写了就没意思了。
先说结论:题目里两个限制都是假的。
对于要求最后要卖光:
我们只需要尽可能多的携带黄金,不用在意是否能卖光。因为实际情况下我们可以调整购入黄金的量来达到要求。
对于不能丢弃:
我们能买黄金就尽量买,在路途中丢弃黄金和购入时购入恰好的量是等效的。
一个城市有车站的话就向上一个车站连一条边权为 的边,反正最后能缀在一起就行了。
然后按照最大生成树建重构树,求两点之间的 ,用个变量记录一下当前携带的黄金数量模拟就行了。
纯口胡,没写过,写的两个 的树剖。
P4197 Peaks
给定一个无向图,有 个询问,求从点 出发,只经过权值小于等于 的边能到达的所有点中第 大的权值。
kruskal 重构树和主席树缝起来。
按照最小生成树建出重构树,倍增找到点 的祖先中最浅的权值小于等于 的点 ,能到达的所有点就是以 为根的子树的叶子节点。
我们可以通过树剖等知识知道,一棵子树内的所有点的 dfs
序是一个连续的区间。把重构树拍扁,注意只有叶子结点存的是原图的点权,所以每个非叶子结点只需要记录它包含的叶子节点的区间就行了,然后建主席树,查询区间第 大即可。
Code
#include<cstdio> #include<algorithm> using namespace std; const int MAXN = 1e5 + 10, MAXM = 5e5 + 10, SIZE = 25; int n, m, q, cnt, len, lim, sum, num; int head[MAXN << 1], hig[MAXN], data[MAXN]; int val[MAXN << 1], lpos[MAXN << 1], rpos[MAXN << 1]; int root[MAXN]; int fa[MAXN << 1][SIZE]; struct Line{ int from, to, dis; }line[MAXM]; inline bool cmp(const Line &a, const Line &b){ return a.dis < b.dis; } struct Edge{ int to, next; }e[MAXM << 1]; inline void Add(int u, int v){ e[++cnt].to = v; e[cnt].next = head[u]; head[u] = cnt; } inline int read(){ int x = 0, f = 1; char c = getchar(); while(c < '0' || c > '9'){ if(c == '-') f = -1; c = getchar(); } while(c >= '0' && c <= '9'){ x = (x << 1) + (x << 3) + (c ^ 48); c = getchar(); } return x * f; } struct Union_Set{ int fa[MAXN << 1]; void init(int n){ for(register int i = 1; i <= n; i++) fa[i] = i; } int Find(int x){ return x == fa[x] ? x : fa[x] = Find(fa[x]); } }U; struct Chairman_Tree{ int tot; struct Tree{ int lson, rson; int size; }tr[MAXN * 50]; #define lson(x) tr[x].lson #define rson(x) tr[x].rson void Build(int &rt, int l, int r){ rt = ++tot; if(l == r) return; int mid = (l + r) >> 1; Build(lson(rt), l, mid); Build(rson(rt), mid + 1, r); } void Update(int &rt, int last, int pos, int L, int R){ rt = ++tot; tr[rt].lson = tr[last].lson; tr[rt].rson = tr[last].rson; tr[rt].size = tr[last].size + 1; if(L == R) return; int mid = (L + R) >> 1; if(pos <= mid) Update(lson(rt), lson(last), pos, L, mid); else Update(rson(rt), rson(last), pos, mid + 1, R); } int Query_Kth(int rt_l, int rt_r, int k, int L, int R){ if(tr[rt_r].size - tr[rt_l].size < k) return -1; if(L == R) return L; int mid = (L + R) >> 1; int cnt = tr[lson(rt_r)].size - tr[lson(rt_l)].size; if(k <= cnt) return Query_Kth(lson(rt_l), lson(rt_r), k, L, mid); else return Query_Kth(rson(rt_l), rson(rt_r), k - cnt, mid + 1, R); } }C; void Kruscal(){ int tot = 0; U.init(n << 1); sort(line + 1, line + 1 + m, cmp); for(register int i = 1; i <= m; i++){ int u = line[i].from, v = line[i].to; int fa_u = U.Find(u), fa_v = U.Find(v); if(fa_u != fa_v){ ++tot; val[++sum] = line[i].dis; U.fa[fa_u] = U.fa[fa_v] = sum; Add(sum, fa_u), Add(fa_u, sum); Add(sum, fa_v), Add(fa_v, sum); } if(tot == n - 1) break; } } void dfs(int rt, int father){ lpos[rt] = num; fa[rt][0] = father; for(register int i = 1; fa[rt][i - 1]; i++) fa[rt][i] = fa[fa[rt][i - 1]][i - 1]; int son = 0; for(register int i = head[rt]; i; i = e[i].next){ int v = e[i].to; if(v == father) continue; ++son; dfs(v, rt); } if(!son){ ++num; C.Update(root[num], root[num - 1], hig[rt], 1, lim); } rpos[rt] = num; } int Query(int v, int x, int k){ for(register int i = 24; i >= 0; i--) if(fa[v][i] && val[fa[v][i]] <= x) v = fa[v][i]; k = rpos[v] - lpos[v] - k + 1; if(k < 1) return -1; return C.Query_Kth(root[lpos[v]], root[rpos[v]], k, 1, lim); } int main(){ n = read(), m = read(), q = read(), sum = n; for(register int i = 1; i <= n; i++) hig[i] = read(), data[i] = hig[i]; sort(data + 1, data + 1 + n); len = unique(data + 1, data + 1 + n) - data - 1; for(register int i = 1; i <= n; i++){ int pos = lower_bound(data + 1, data + 1 + len, hig[i]) - data; hig[i] = pos, lim = max(lim, pos); } for(register int i = 1; i <= m; i++){ int u, v, w; u = read(), v = read(), w = read(); line[i] = (Line){u, v, w}; } Kruscal(); C.Build(root[0], 1, lim); dfs(sum, 0); for(register int i = 1; i <= q; i++){ int v, x, k, ans; v = read(), x = read(), k = read(); ans = Query(v, x, k); if(ans != -1) ans = data[ans]; printf("%d\n", ans); } return 0; }
P7834 [ONTAK2010] Peaks 加强版
只加强了一个强制在线,然而 kruskal 重构树就是在线做法。
[AGC002D] Stamp Rally
给定一张无向图,每次询问从 和 分别出发,一共要经过 个点(经过相同的点只算一次),使得走过编号最大的边最小。
以边的编号为权值,按照最小生成树建重构树,每次二分需要走过的最大的编号,让 , 分别跳到最浅的满足要求的祖先。则以该节点为根的子树中的叶子结点都是可以到达的,判断两节点包含的叶子结点的数量是否大于等于 即可。
Code
#include<cstdio> #include<algorithm> using namespace std; const int MAXN = 1e5 + 10, SIZE = 25; int n, m, q, cnt, sum, maxn; int head[MAXN << 1], val[MAXN << 1], siz[MAXN << 1]; int fa[MAXN << 1][SIZE]; struct Line{ int from, to, dis; }line[MAXN]; bool cmp(const Line &a, const Line &b){ return a.dis < b.dis; } struct Edge{ int to, next; }e[MAXN << 2]; inline void Add(int u, int v){ e[++cnt].to = v; e[cnt].next = head[u]; head[u] = cnt; } inline int read(){ int x = 0, f = 1; char c = getchar(); while(c < '0' || c > '9'){ if(c == '-') f = -1; c = getchar(); } while(c >= '0' && c <= '9'){ x = (x << 1) + (x << 3) + (c ^ 48); c = getchar(); } return x * f; } struct Union_Set{ int fa[MAXN << 1]; void init(int n){ for(register int i = 1; i <= n; i++) fa[i] = i; } int Find(int x){ return fa[x] == x ? x : fa[x] = Find(fa[x]); } }U; void Kruscal(){ int tot = 0; U.init(n << 1); sort(line + 1, line + 1 + m, cmp); for(register int i = 1; i <= m; i++){ int u = line[i].from, v = line[i].to; int fa_u = U.Find(u), fa_v = U.Find(v); if(fa_u != fa_v){ ++tot; val[++sum] = line[i].dis; maxn = max(maxn, val[sum]); U.fa[fa_u] = U.fa[fa_v] = sum; Add(sum, fa_u), Add(fa_u, sum); Add(sum, fa_v), Add(fa_v, sum); } } } void dfs(int rt, int father){ fa[rt][0] = father; for(register int i = 1; fa[rt][i - 1]; i++) fa[rt][i] = fa[fa[rt][i - 1]][i - 1]; int son = 0; for(register int i = head[rt]; i; i = e[i].next){ int v = e[i].to; if(v == father) continue; ++son, dfs(v, rt); siz[rt] += siz[v]; } if(!son) ++siz[rt]; } bool Check(int x, int y, int num, int tot){ for(register int i = 24; i >= 0; i--) if(fa[x][i] && val[fa[x][i]] <= num) x = fa[x][i]; for(register int i = 24; i >= 0; i--) if(fa[y][i] && val[fa[y][i]] <= num) y = fa[y][i]; if(x == y) return (siz[x] >= tot); else return (siz[x] + siz[y] >= tot); } int Query(int x, int y, int z){ int l = 1, r = maxn, ans = 0; while(l <= r){ int mid = (l + r) >> 1; if(Check(x, y, mid, z)) ans = mid, r = mid - 1; else l = mid + 1; } return ans; } int main(){ n = read(), m = read(), sum = n; for(register int i = 1; i <= m; i++){ int u, v, w; u = read(), v = read(), w = i; line[i] = (Line){u, v, w}; } Kruscal(); dfs(sum, 0); q = read(); for(register int i = 1; i <= q; i++){ int x, y, z; x = read(), y = read(), z = read(); printf("%d\n", Query(x, y, z)); } return 0; }
P3684 [CERC2016]机棚障碍 Hangar Hurdles
太长,不想写了。
对于每个位置求出它能通过的最大的矩形大小,转化成求最大瓶颈路问题。
求最大矩形大小可以二分查找。记录一个二维前缀和 ,遇到障碍物加 ,设当前点为 ,二分 ,使得 到 的和为 ,最后最大的矩形就是 。
然后去连边,边权是两端点能通过的最大的矩形的最小值。从当前点分别向它的右边和下边连边即可。注意的是障碍物也需要连边,否则一些不能通达的点不会出现在重构树里,查询时会寄掉。有障碍物只需要把边权设为 即可。
按照最大生成树去建重构树,询问就是求两点之间的 ,如果 的权值是 的话说明不能通达,判掉即可。
Code
#include<cstdio> #include<algorithm> #define X first #define Y second #define Pair pair< int, int > #define Make(x, y) make_pair(x, y) using namespace std; const int MAXN = 1010, MAXM = 1e6 + 10, SIZE = 35; int n, m, q, cnt, all, tot, teg; int lim[MAXM], head[MAXM << 1], val[MAXM << 1]; int fa[MAXM << 1], son[MAXM << 1], deep[MAXM << 1], size[MAXM << 1]; int top[MAXM << 1], dfn[MAXM << 1]; int num[MAXN][MAXN], sum[MAXN][MAXN]; char map[MAXN][MAXN]; struct Edge{ int to, next; }e[MAXM << 2]; inline void Add(int u, int v){ e[++cnt].to = v; e[cnt].next = head[u]; head[u] = cnt; } struct Line{ int from, to, dis; }line[MAXM << 2]; bool cmp(const Line &a, const Line &b){ return a.dis > b.dis; } inline int read(){ int x = 0, f = 1; char c = getchar(); while(c < '0' || c > '9'){ if(c == '-') f = -1; c = getchar(); } while(c >= '0' && c <= '9'){ x = (x << 1) + (x << 3) + (c ^ 48); c = getchar(); } return x * f; } bool Check(int x, int y, int len){ int lx = x - len, ly = y - len; int rx = x + len, ry = y + len; return sum[rx][ry] - sum[lx - 1][ry] - sum[rx][ly - 1] + sum[lx - 1][ly - 1] == 0; } int Lower_Bound(int x, int y){ int l = 0, r = min(min(x - 1, n - x), min(y - 1, n - y)); int ans = 0; while(l <= r){ int mid = (l + r) >> 1; if(Check(x, y, mid)) ans = mid, l = mid + 1; else r = mid - 1; } return ans; } struct Union_Set{ int fa[MAXM << 1]; void init(int n){ for(register int i = 1; i <= n; i++) fa[i] = i; } int Find(int x){ return x == fa[x] ? x : fa[x] = Find(fa[x]); } }U; void Kruscal(){ int num = 0; all = tot; U.init(tot << 1); sort(line + 1, line + 1 + m, cmp); for(register int i = 1; i <= m; i++){ int u = line[i].from, v = line[i].to, w = line[i].dis; int fa_u = U.Find(u), fa_v = U.Find(v); if(fa_u != fa_v){ ++num; val[++all] = w; U.fa[fa_u] = U.fa[fa_v] = all; Add(all, fa_u), Add(fa_u, all); Add(all, fa_v), Add(fa_v, all); } if(num == tot - 1) break; } } void dfs_deep(int rt, int father, int depth){ size[rt] = 1; fa[rt] = father; deep[rt] = depth; int max_son = -1; for(register int i = head[rt]; i; i = e[i].next){ int v = e[i].to; if(v == father) continue; dfs_deep(v, rt, depth + 1); size[rt] += size[v]; if(size[v] > max_son){ son[rt] = v; max_son = size[v]; } } } void dfs_top(int rt, int top_fa){ dfn[rt] = ++teg; top[rt] = top_fa; if(!son[rt]) return; dfs_top(son[rt], top_fa); for(register int i = head[rt]; i; i = e[i].next){ int v = e[i].to; if(!dfn[v]) dfs_top(v, v); } } int Get_LCA(int x, int y){ while(top[x] != top[y]){ if(deep[top[x]] < deep[top[y]]) swap(x, y); x = fa[top[x]]; } if(deep[x] > deep[y]) swap(x, y); return x; } int main(){ n = read(); for(register int i = 1; i <= n; i++){ scanf("%s", map[i] + 1); for(register int j = 1; j <= n; j++){ num[i][j] = ++tot; sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1]; if(map[i][j] == '#') ++sum[i][j]; } } for(register int i = 1; i <= n; i++){ for(register int j = 1; j <= n; j++){ if(map[i][j] == '.'){ int len = Lower_Bound(i, j); lim[num[i][j]] = len * 2 + 1; } else lim[num[i][j]] = 0; val[num[i][j]] = lim[num[i][j]]; } } for(register int i = 1; i <= n; i++){ for(register int j = 1; j <= n; j++){ if(j < n) line[++m] = (Line){num[i][j], num[i][j + 1], min(lim[num[i][j]], lim[num[i][j + 1]])}; if(i < n) line[++m] = (Line){num[i][j], num[i + 1][j], min(lim[num[i][j]], lim[num[i + 1][j]])}; } } Kruscal(); dfs_deep(all, 0, 1); dfs_top(all, all); q = read(); for(register int i = 1; i <= q; i++){ Pair a, b; int x, y, anc; a.X = read(), a.Y = read(), b.X = read(), b.Y = read(); x = num[a.X][a.Y], y = num[b.X][b.Y]; anc = Get_LCA(x, y); printf("%d\n", val[anc]); } return 0; }
还有CF上的两道题,但我找不到了,咕了。
以下为博客签名,与博文无关。
只要你们不停下来,那前面就一定有我。所以啊,不要停下来~
本文来自博客园,作者:TSTYFST,转载请注明原文链接:https://www.cnblogs.com/TSTYFST/p/16889695.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!