洛谷P3336【模版】最小生成树 题解(kruskal版)

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
//并查集(DSU)结构体
struct DSU{
	vector<int> parent;//核心数组,记录每个点的祖宗
	//构造函数:对象诞生时自动调用!
	DSU(int n) : parent(n+1){//初始化列表:直接创建大小为n+1的vector
		for(int i = 1 ; i<=n ; i++)
			parent[i] = i;//每个节点初始的祖宗是自己 
		//此时parent数组实例(n=3):[?,1,2,3] (?表示未使用的parent[0])
	}
	//查找函数 :找x的终极祖宗+路径压缩
	int find(int x) {
		if(parent[x]!=x)//如果x不是祖宗 
			parent[x] = find(parent[x]);
		return parent[x];
	}
	//合并函数:把x和y所在的家族合并
	bool unionSets(int x , int y) {
		int xRoot = find(x);//找到x的祖宗 
		int yRoot = find(y);//找到y的祖宗
		if(xRoot==yRoot) return false;//本来就是一家的,不用合并
		parent[yRoot]  = xRoot;//让y的祖宗认x的祖宗当亲爹
		return true;//返回合并成功 
	}
}; 
//Kruskal 算法主函数
int Kruskal(int n , vector<vector<int>>& edges) {
	//1.贪心核心,所有边按权值从大到小排序
	sort(edges.begin(),edges.end(),[](const vector<int> &a,const vector<int> &b) {
		return a[2]<b[2];//比较每条边的第三个元素(权值) 
	});
	DSU dsu(n) ;
	int totalWeight = 0;//记录最小生成树的总权值
	int edgesUsed = 0;//记录已使用的边数(目标:n-1条)
	//2.遍历排序后的所有边
	for(const auto&edge:edges) {
		int u = edge[0];//边的起点
		int v = edge[1];//边的终点
		int w = edge[2];//边的权值 
		//3.尝试合并u和v所在的集合
		if(dsu.unionSets(u,v)) {//如果合并成功(未成环) 
			totalWeight += w;//累加当前权值 
			edgesUsed++;//已用边数+1
			if(edgesUsed==n-1)  break;//够数了,提前退出 
		}
	}
	//4.检查是否联通(边数是否够n-1) 
	return (edgesUsed==n-1)?totalWeight:-1;
}
//main函数(程序入口) 
int main()
{
	//输入部分
	int n , m;//n=顶点数,m=边数
	cin>>n>>m;
	vector<vector<int>> edges(m,vector<int>(3)) ;//创建m行三列的二维数组 
	for(auto i = 0 ; i<m ; i++) 
		cin>>edges[i][0]>>edges[i][1]>>edges[i][2];//读每条边的u,v,w
	int result = Kruskal(n,edges) ;
	if(result==-1)
		cout<<"orz"<<endl;
	else
		cout<<result<<endl;
	return 0;
}

<think>首先,用户的问题是关于 P3367 的解题代码和实现思路。用户提到了“ P3367”,这是一个编程题目编号,我需要提供相关的程序实现或解题思路。 从系统指令中,我有一些引用:[^1], [^2], [^3]。这些引用提供了关于 P3367 的信息。引用[1] 提供了C++代码,引用[2] 提供了Java题解,包括时间复杂度和优化说明,引用[3] 讨论了合并操作。 用户的具体问题是:“请问 P3367 解题代码 实现思路”。所以,我需要提供解题代码和实现思路。 P3367 的题目是“【模板】并查集”,这是一个关于并查集(Union-Find)数据结构的模板题。并查集用于处理集合的合并与查询问题。 我的回答结构需要清晰,帮助用户逐步解决问题。既然用户要求“解题代码”和“实现思路”,我应该先解释思路,再提供代码。 从引用中: - 引用[1] 提供了C++代码。 - 引用[2] 提供了Java题解和优化说明。 - 引用[3] 讨论了合并操作。 我需要确保回答真实可靠,所以基于这些引用提供信息。 实现思路: - 并查集的核心操作包括:查找(find)、合并(union)、检查连接(check)。 - 优化:使用路径压缩(path compression)来优化查找操作。 - 时间复杂度:查找和合并操作近似O(α(n)),其中α是反阿克曼函数,几乎是常数时间。 - 空间复杂度:O(n),用于存储父节点数组。 代码:我可以提供C++代码,因为引用[1] 有完整的代码。引用[1] 的代码是: ```cpp #include<iostream> #include<algorithm> #include<vector> using namespace std; const int N = 2e5+10; int fa[N]; int find(int i) { if(fa[i] != i) { fa[i] = find(fa[i]); } return fa[i]; } void set(int u,int v) { int u1 = find(u); int v1 = find(v); if(u1 != v1) { fa[v1] = u1; } } bool check(int u,int v) { return find(u) == find(v); } int main() { ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); int n,m; cin >> n >> m; for(int i = 1 ; i <= N ; i++) fa[i] = i; for(int i = 1 ; i <= m ; i++) { int z,x,y; cin >> z >> x >> y; if(z == 1) { set(x,y); } else if(z == 2) { if(check(x,y)) { cout << 'Y' << '\n'; } else { cout <<'N' << '\n'; } } } return 0; } ``` 在回答中,我需要: - 使用中文回答。 - 对于数学表达式,使用$...$或$$...$$格式。但在并查集上下文中,可能没有太多数学,但如果有,比如时间复杂度,我可以使用行内数学。 - 生成相关问题:在最后,添加相关问题部分。 - 引用的段落末尾添加引用标识,如[^1]。 结构: 1. **实现思路**:解释并查集的基本概念和操作。 2. **解题代码**:提供C++代码(或可选其他语言),并解释关键部分。 3. **相关问题**:生成一些相关问题。 相关问题应该基于主题,比如: 1. 并查集的其他优化(如按秩合并)。 2. 并查集的应用场景。 3. 如何测试并查集的性能。 最后,确保引用正确:在解释思路时,如果基于引用,添加标识。 现在,构建回答: - 开头:介绍题目。 - 思路:描述并查集的find, union, check操作,优化(路径压缩),时间复杂度。 - 代码:提供C++代码,并简要说明。 - 相关问题:列出几个问题。 引用添加:例如,当提到路径压缩时,引用[2]提到优化说明。</think>### P3367 【模板】并查集解题思路与代码 P3367 是一个经典的并查集(Union-Find)模板题,用于处理集合的合并与查询操作。并查集是一种高效的数据结构,常用于解决动态连通性问题,如网络连接检测或图论中的组件合并。下面我将逐步解释实现思路,并提供完整的解题代码。 #### 实现思路 1. **核心操作**: - **查找(Find)**:确定元素所属的集合代表(根节点)。使用路径压缩优化(Path Compression),在查找过程中将节点直接连接到根节点,减少后续查找深度。时间复杂度近似 $O(\alpha(n))$,其中 $\alpha$ 是反阿克曼函数,可视为常数时间[^2]。 - **合并(Union)**:将两个元素所在的集合合并为一个集合。先查找各自的根节点,然后连接它们(例如,将其中一个根节点指向另一个)。时间复杂度同样为 $O(\alpha(n))$[^2]。 - **检查(Check)**:判断两个元素是否属于同一集合(即是否连通),通过比较它们的根节点实现。时间复杂度 $O(\alpha(n))$[^2]。 2. **优化方法**: - **路径压缩**:在查找操作中扁平化树结构,确保后续操作更快。引用[1] 的代码中,`find` 函数递归更新父节点来实现这一点。 - **高效 I/O**:处理大规模输入时,使用缓冲读写(如 C++ 中的 `ios::sync_with_stdio(0)`)避免超时[^1]。 - **初始化**:每个元素初始化为自己的父节点(即独立集合),空间复杂度 $O(n)$[^2]。 3. **整体流程**: - 读取输入:元素数量 $n$ 和操作数 $m$。 - 处理操作:根据指令类型($z=1$ 为合并,$z=2$ 为查询)调用相应函数。 - 输出结果:对于查询操作,输出 "Y"(连通)或 "N"(不连通)。 #### 解题代码(C++实现) 以下代码基于引用[1] 的实现,并添加了详细注释。代码使用路径压缩优化,确保高效性。 ```cpp #include <iostream> #include <algorithm> using namespace std; const int N = 2e5 + 10; // 最大元素数量,根据题目要求设置 int fa[N]; // 存储每个元素的父节点 // 查找操作:带路径压缩 int find(int i) { if (fa[i] != i) { fa[i] = find(fa[i]); // 递归压缩路径 } return fa[i]; } // 合并操作:将元素u和v所在的集合合并 void setUnion(int u, int v) { int uRoot = find(u); // 找到u的根节点 int vRoot = find(v); // 找到v的根节点 if (uRoot != vRoot) { fa[vRoot] = uRoot; // 将v的根节点指向u的根节点(简单合并) } } // 检查操作:判断u和v是否连通 bool check(int u, int v) { return find(u) == find(v); // 比较根节点是否相同 } int main() { ios::sync_with_stdio(0); // 加速输入输出 cin.tie(0); cout.tie(0); int n, m; cin >> n >> m; // 读取元素数n和操作数m // 初始化:每个元素为独立集合 for (int i = 1; i <= n; i++) { fa[i] = i; } // 处理m个操作 for (int i = 0; i < m; i++) { int z, x, y; cin >> z >> x >> y; // 读取操作类型和参数 if (z == 1) { setUnion(x, y); // 合并操作 } else if (z == 2) { // 查询操作,输出结果 if (check(x, y)) { cout << "Y\n"; } else { cout << "N\n"; } } } return 0; } ``` **代码说明**: - **时间复杂度**:所有操作(查找、合并、检查)平均为 $O(\alpha(n))$,近似常数时间,适合处理大规模数据(如 $n \leq 2 \times 10^5$)[^2]。 - **空间复杂度**:$O(n)$,仅需存储父节点数组。 - **关键优化**:路径压缩在 `find` 函数中实现,显著提升性能。引用[3] 强调合并操作通过根节点连接来避免树退化[^3]。 - **输入输出**:使用缓冲读写加速,避免超时(常见于在线评测)。 #### 性能与应用 - **性能分析**:在随机数据下,并查集效率极高,但最坏情况(如链状结构)可能退化。路径压缩确保平均高效。 - **应用场景**:图论连通性问题(如 Kruskal 算法中的最小生成树)、动态网络连接检测或游戏开发中的对象分组[^2]。 如果您有其他语言需求(如 Java),我可以基于引用[2] 提供实现。或者,您需要优化(如按秩合并)的代码,请告知!
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值