首页 > 其他分享 >克鲁斯卡尔重构树

克鲁斯卡尔重构树

时间:2024-05-18 16:09:56浏览次数:20  
标签:重构 int siz 路径 克鲁斯 卡尔 权值 find

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

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

H.Sum of Maximum Weights

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

官方Solution:

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

  • 处理每一条边的时候,由于树,这条边的起点和终点一定连通,所以我们设置两个组 \(U\) 和 \(V\) ,
    那么 \(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\ $ 个顶点, $n-1\ $ 条边。每个顶点被染成了白色或者黑色。 maki想知道,取两个不同的点,它们的简单路径上有且仅有一个黑色点的取法有多少? 注: ①树上两点简单路径指连接两点的最短路。 ② $\ $ 和 \(<p,q>\)和\(<q,p>\)的取法视为同一种。

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

  • 这样第一种路径的数量为\(\mathop{ \sum }\limits_{{i=1}}^{{k}}{{\mathop{ \sum }\limits_{{j=i+1}}^{{k}}{f \left( i \left) *f \left( j \right) \right. \right. }}}\)(注意可以用前缀和处理达成 \(O(k)\) 计算)
  • 第二种路径的数量为\({\mathop{ \sum }\limits_{{i=1}}^{{k}}{f \left( i \right) }}\)。

先给出一道克鲁斯卡尔重构树的板子题[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

标签:重构,int,siz,路径,克鲁斯,卡尔,权值,find
From: https://www.cnblogs.com/mathiter/p/18199411

相关文章

  • 京东秒送售后系统退款业务重构心得| 京东零售技术团队
    一、重构背景1.1、退款京东秒送秒送退款有2套结构,代码逻辑混乱;其中秒送、天选部分售后单是和平生pop交互退款,部分是和售后中台交互退款;并且兼容3套逻辑;痛点:代码繁重,缺乏合理性的设计,后续迭代开发以及维护成本高,同时增加了系统的风险和不稳定性1.2、金额计算京东秒送两套独......
  • ddd和重构
    在学习ddd的时候,有最大一个困惑就是:我应该把哪些抽象成为领域,哪些是作为一个聚合根呢?一个能够完全驾驭某个系统DDD架构的人,他必须是领域专家+代码抽象高手,很明显这个不是一个容易的事情。我们现在很多时候是“面向数据库表”编程,很多时候一个表就对应着一个model,然后就dao->serv......
  • [NOI2009] 二叉查找树 & 笛卡尔树学习笔记
    这个题:二叉搜索树原理认识+区间dp;只要熟练相关算法就一定可以做出来。但我不行。。。我们学习一下笛卡尔树:什么垃圾东西,不学了。发现这个题是l蓝书上一道题jqb。二叉查找树又有一个性质:二叉查找树的中序遍历是其代表的序列从小到大排序的结果。而无论Treap如何旋转,其都......
  • 笛卡尔树学习笔记
    笛卡尔树引入是一种二叉树,每个节点由一个二元组\((k,w)\)形成。\(k\)满足二叉搜索树的性质,\(w\)满足堆的性质。上面这棵笛卡尔树相当于把数组元素值当作键值\(w\),而把数组下标当作键值\(k\)。显然可以发现,这棵树的键值\(k\)满足二叉搜索树的性质,而键值\(w\)满足小根......
  • 前端架构重构 - 设计文档
    框架选型:React-写React的感觉就是在写JavaScript。但React的缺点是冗余,它只是个View层,也只处理View层。Vue-Vue的定位是一个渐进式的MVVM框架。渐进式,就是学习曲线比较平缓,没那么陡峭。但这并不意味着Vue比React简单,React就比Vue复杂。Vue在框架层面......
  • 从零开始写 Docker(十四)---重构:实现容器间 rootfs 隔离
    本文为从零开始写Docker系列第十四篇,实现容器间的rootfs隔离,使得多个容器间互不影响。完整代码见:https://github.com/lixd/mydocker欢迎Star推荐阅读以下文章对docker基本实现有一个大致认识:核心原理:深入理解Docker核心原理:Namespace、Cgroups和Rootfs基于n......
  • 项目重构经验记录
    需求:目前公司内部有一个项目,leader不想给外包做了,想收回来自己做。我看过之后发现继续重构维护成本有点大,遂决定重构。外包技术栈:前端vue2.0,后端C#,数据库sqlserver由于我既不会C#,也不会sqlserver,所以决定写项目。遇到的第一个难题是,因为项目已经上线有了部分用户数据,该部分的数......
  • Godot.NET C#IOC重构(11):攻击与死亡
    目录前言带上伤害HitboxHurtbox实现效果渐变淡出添加受攻击效果Hurtbox完善Enemy状态机结果剩下的都是逻辑完善的部分了,后面的我就跳过了。前言这次来深刻了解一下Godot中的伤害计算带上伤害我们将之前的Hitbox和HurtBox进行了一下简单的修改HitboxusingGodot;usingSyste......
  • Godot.NET C#IOC重构(9-10):三连击,攻击框
    目录前言AnimationPlayer和AnimatedSprite2D将导出属性添加到关键帧里面。状态机构建核心代码完整代码实现效果碰撞框和受攻击框全局类HitBox:攻击框HurtBox:受击框实现效果添加Player攻击总结前言这篇博客来深入讲解一下Godot中的AnimationPlayerAnimationPlayer和AnimatedSpr......
  • 重构-重构方式
    1、重新组织函数1.1 提炼函数ExtractMethod*概述:将一段代码放入一个独立函数中,并让函数名称解释该函数的用途。*动机:厌恶过长的代码。如果函数过长,则很难被理解(用途不清晰,需要大段的注释描述意图),测试也困难;相反,如果一个函数够小、有清晰意图的命名,则被复用的机会更大,函......