克鲁斯卡尔重构树

一类以并查集在建树过程中维护各种信息的值——克鲁斯卡尔重构树前身

第一次见到是在zzu的校赛中,印象深刻

H.Sum of Maximum Weights

题意:给定一棵树,求树上任意两点间最短路径中的最大边权的sum

官方Solution:

  • 我们先将边按权值排序,这样每次处理的都是当前的最大权值

  • 处理每一条边的时候,由于树,这条边的起点和终点一定连通,所以我们设置两个组 UV
    那么 U 组中任意成员到 V 组中任意成员的最大路径边权必为当前边的权值,设为 e,故可以写出 ans +=siz[u]×siz[v]×e
    然后将二组合并,之后对每条边考虑上述情况后即可得出答案

我的理解:如果了解kruskal重构树,这个就是板子题。

```cpp #include <bits/stdc++.h> #define int long long using namespace std; const int N = 2e6 + 5; const int M = N * 2; int a[N], b[N]; int f[N]; int siz[N]; int Get(int u) { return u == f[u] ? u : f[u] = Get(f[u]); } struct edge { int u, v, e; } e[N]; bool cmp(edge x, edge y) { return x.e < y.e; } signed main() { int n; cin >> n; for (int i = 1; i < n; i++) { scanf("%lld %lld %lld", &e[i].u, &e[i].v, &e[i].e); } for (int i = 1; i <= n; i++) { f[i] = i; siz[i] = 1; } sort(e + 1, e + n, cmp); int ans = 0; for (int i = 1; i < n; i++) { edge t = e[i]; int u = t.u, v = t.v, e = t.e; u = Get(u), v = Get(v); if (siz[u] < siz[v]) { swap(u, v); } ans += siz[u] * siz[v] * e; f[u] = v; siz[v] += siz[u]; } cout << ans << endl; } ```

牛客小白月赛25C

https://ac.nowcoder.com/acm/contest/5600/C

题意:树上有黑白点,可以将一个黑点改成白点,求修改后最大白色连通块。

Solution:首先考虑对于白色连通块用并查集维护连通性和size。暴力枚举黑色节点,累加他相邻的白色连通块大小,取max就是答案

debug:字符串下标和数组下标混用。特判全是白色节点的情况

int n, m; int a[N]; int col[N]; struct DSU { vector<int> f, siz; DSU() {} DSU(int n) { init(n); } void init(int n) { f.resize(n); std::iota(f.begin(), f.end(), 0); siz.assign(n, 1); } int find(int x) { while (x != f[x]) { x = f[x] = f[f[x]]; } return x; } bool same(int x, int y) { return find(x) == find(y); } bool merge(int x, int y) { x = find(x); y = find(y); if (x == y) { return false; } siz[x] += siz[y]; f[y] = x; return true; } int size(int x) { return siz[find(x)]; } }; vector<int>e[N]; void solve(){ cin>>n; string s;cin>>s; for(int i=0;i<s.size();i++){ if(s[i]=='W')col[i+1]=1; else col[i+1]=0; } DSU dsu(n+1); for(int i=1;i<=n-1;i++){ int u,v;cin>>u>>v; e[u].push_back(v); e[v].push_back(u); if(col[u]&&col[v])dsu.merge(u,v); } //DSU dsu(n+1); // for(int i=1;i<=n;i++){ // if(col[i]==0)continue; // for(auto v:e[i]){ // if(col[v])dsu.merge(i,v); // } // } int ans=0; for(int i=1;i<=n;i++){ int sum=0; if(col[i]==0){ for(auto v:e[i]){ if(col[v]==1)sum+=dsu.siz[dsu.find(v)]; } ans=max(ans,sum+1); } } if(ans==0)ans=n; cout<<ans<<endl; }

2020牛客寒假算法基础集训营1 F

有一天,maki拿到了一颗树。所谓树,即没有自环、重边和回路的无向连通图。 这个树有 n  个顶点, n1  条边。每个顶点被染成了白色或者黑色。 maki想知道,取两个不同的点,它们的简单路径上有且仅有一个黑色点的取法有多少? 注: ①树上两点简单路径指连接两点的最短路。 ②  <p,q><q,p>的取法视为同一种。

Solution:经过一个黑点的路径有两种:两个端点都是白点;其中一个端点是黑点。 因此我们可以先预处理,将每个白点连通块上的白点个数统计出来。这样我们就可以得知每个黑点所连接的白点的权值(即连通块白点数)。 设某黑点连接了 k 个白点,第 i 个白点的权值为 f(i)

  • 这样第一种路径的数量为i=1kj=i+1kf(i)f(j)(注意可以用前缀和处理达成 O(k) 计算)
  • 第二种路径的数量为i=1kf(i)

先给出一道克鲁斯卡尔重构树的板子题[P1967 NOIP2013 提高组] 货车运输 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题意:给出一张普通无向图,多次查询。每次查询的具体内容是,给出两个点,求连通两个点的所有路径中,每条路径都有最小边权值,求在这些最小边权值中最大的那个值

Solution:首先考虑路径问题,涉及多条路径不能直接做。我们考虑如果找路径,我们先跑最大生成树,然后发现最大生成树上路径最小值就是答案。

问题转化为如何快速求出树上任意两点的路径最小值,一种比较重工业的做法是:树链剖分加线段树维护区间最小值。这里给出克鲁斯卡尔重构树的解法。

在克鲁斯卡尔生成树的过程中,每当两个点要很合并的时候,我们都开一个新点,注意并查集合并的过程中,我们开一个新点,把两个要合并的点都作为子节点,其中需要刻意维护父节点关系,让新点做父节点,新点的权值是本次合并初始两点的边的权值权重,保证生成的克鲁斯卡尔重构树是正确的

经过上面的建树,每次查询只需要找到这个两个点的lca,然后lca的权值就是答案。

int n, m; int a[N]; vector<int> e[N]; int fa[N],son[N],dep[N],siz[N]; int top[N]; void dfs1(int u,int f){ //搞fa,dep,son fa[u]=f;siz[u]=1;dep[u]=dep[f]+1; for(int v:e[u]){ if(v==f) continue; dfs1(v,u); siz[u]+=siz[v]; if(siz[son[u]]<siz[v])son[u]=v; } } void dfs2(int u,int t){ //搞top top[u]=t; //记录链头 if(!son[u]) return; //无重儿子 dfs2(son[u],t); //搜重儿子 for(int v:e[u]){ if(v==fa[u]||v==son[u])continue; dfs2(v,v); //搜轻儿子 } } int lca(int u,int v){ while(top[u]!=top[v]){ if(dep[top[u]]<dep[top[v]])swap(u,v); u=fa[top[u]]; } return dep[u]<dep[v]?u:v; } void add(int u,int v){ e[u].push_back(v); } struct edges{ int u,v,w; bool operator<(const edges &tmp){ return w>tmp.w; } }; edges edge[M]; void solve(){ int q; cin>>n>>m; DSU dsu(2*n+1); for(int i=1;i<=m;i++){ int u,v,w; cin>>u>>v>>w; //原图根本不要了 //e[u].push_back(v); //e[v].push_back(u); edge[i]={u,v,w}; } sort(edge+1,edge+1+m); int cnt=n; for(int i=1;i<=m;i++){ auto [u,v,w]=edge[i]; //cerr<<w<<endl; u=dsu.find(u),v=dsu.find(v); if(u!=v){ cnt++; //这里合并的时候注意父节点 dsu.merge(cnt,u); dsu.merge(cnt,v); add(cnt,u);add(u,cnt); add(cnt,v);add(v,cnt); a[cnt]=w; } } //build //lca for(int i=1;i<=2*n;i++){ if(dsu.find(i)==i){ dfs1(i,0); dfs2(i,i); } } cin>>q; for(int i=1;i<=q;i++){ int u,v;cin>>u>>v; if(dsu.find(u)!=dsu.find(v)){ cout<<-1<<endl; continue ; } int mid=lca(u,v); cout<<a[mid]<<endl; } }

https://hydro.ac/d/bzoj/p/3732求图中连通两点路径中的最大边的最小值

与上面的题目非常相似,只需要排序时边权从小到大排从而建立重构树

[bzoj 3551 ONTAK2010]Peaks加强版 kruskal重构树+主席树-CSDN博客

https://hydro.ac/d/bzoj/p/3545


__EOF__

本文作者爱飞鱼
本文链接https://www.cnblogs.com/mathiter/p/18199411.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   potential-star  阅读(24)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示