1114 Family Property BFS连通分量和并查集两种做法

家庭属性分析算法
本文介绍了一种用于分析家庭成员间关系及其属性的算法。通过构建图模型,利用BFS和DFS算法来查找家庭成员间的连通分量,并计算每个家庭的平均属性值,如平均面积和平均值。同时,还提供了一种基于并查集的方法实现相同功能。

原题目参见:Family Property
求成员个数,等价求连通分量个数。方法采用BFS,DFS遍历取总分量个数。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <map>
#include <set>
#include <queue>
#include <algorithm>
using namespace std;

const int maxn = 100010;
map<int, int>idtoi; // 99999 - 0;
map<int, int>itoid; // 0 - 99999;
int n = 0;
vector<vector<int> >adj;
double m[maxn];
double area[maxn];
bool vis[maxn] = { 0 };


int geti(int id) {
	//因为有插入vector的操作,所以这个函数只能在处理输入数据的时候使用。
	if (id == -1) {
		return id;
	}
	else if (idtoi.count(id)) {
		return idtoi[id];
	}
	else {
		idtoi[id] = n++;
		itoid[n - 1] = id;
		vector<int> emp;
		adj.push_back(emp);
		return idtoi[id];
	}
}

struct Family {
	int id, num;
	double mm, areaa;
	void print() {
		printf("%04d %d %.3lf %.3lf\n", id, num, mm, areaa);
	}
};

Family BFS(int s) {
	int num = 0;
	double mm = 0, areaa = 0;
	vector<int> v;
	queue<int> q;

	num++;
	vis[s] = 1;
	v.push_back(itoid[s]);
	areaa += area[s];
	mm += m[s];
	q.push(s);

	while (!q.empty()) {
		s = q.front();
		q.pop();
		for (auto fri : adj[s]) {
			if (!vis[fri]) {
				num++;
				vis[fri] = 1;
				v.push_back(itoid[fri]);
				areaa += area[fri];
				mm += m[fri];
				q.push(fri);
			}
		}
	}

	sort(v.begin(), v.end());
	int father = v[0];
	areaa /= num;
	mm /= num;
	Family fam;
	fam.id = father;
	fam.num = num;
	fam.mm = mm;
	fam.areaa = areaa;
	return fam;
}

int cmp(Family a, Family b) {
	if (a.areaa != b.areaa) {
		return a.areaa > b.areaa;
	}
	else {
		return a.id < b.id;
	}
}

int BFSTravel() {
	vector<Family> families;
	Family family;
	for (int i = 0; i < n; i++) {
		if (!vis[i]) {
			family = BFS(i);
			families.push_back(family);
		}
	}
	sort(families.begin(), families.end(), cmp);
	printf("%d\n", families.size());
	for (auto fff : families) {
		fff.print();
	}
	return 0;
}

int main()
{
	fill(m, m + maxn, 0);
	fill(area, area + maxn, 0);
	int N, id, father, mother, k, child, icc;
	double mm, areaa;
	scanf("%d", &N);
	for (int i = 0; i < N; i++) {
		scanf("%d%d%d%d", &id, &father, &mother, &k);
		id = geti(id);
		father = geti(father);
		mother = geti(mother);
		if (father != -1) {
			adj[id].push_back(father);
			adj[father].push_back(id);
		}
		if (mother != -1) {
			adj[id].push_back(mother);
			adj[mother].push_back(id);
		}
		for (int j = 0; j < k; j++) {
			scanf("%d", &child);
			child = geti(child);
			adj[id].push_back(child);
			adj[child].push_back(id);
		}
		scanf("%lf%lf", &m[id], &area[id]);
	}
	BFSTravel();
	return 0;
}

// 运行程序: Ctrl + F5 或调试 >“开始执行(不调试)”菜单
// 调试程序: F5 或调试 >“开始调试”菜单

// 入门使用技巧: 
//   1. 使用解决方案资源管理器窗口添加/管理文件
//   2. 使用团队资源管理器窗口连接到源代码管理
//   3. 使用输出窗口查看生成输出和其他消息
//   4. 使用错误列表窗口查看错误
//   5. 转到“项目”>“添加新项”以创建新的代码文件,或转到“项目”>“添加现有项”以将现有代码文件添加到项目
//   6. 将来,若要再次打开此项目,请转到“文件”>“打开”>“项目”并选择 .sln 文件

求成员个数,等价并查集个数。参见1114 Family Property

#define _CRT_SECURE_NO_WARNINGS
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
const int maxn = 10010;

struct Node {
	int id, count=0, area=0, set=0;
};
vector<Node> vec(maxn);
vector<Node> roots(maxn);
vector<Node> ans;
int father[maxn];

int findFather(int x) {
	int root = x;
	while (root != father[root])root = father[root];
	while (father[x] != root) {
		int z = father[x];
		father[x] = root;
		x = z;
	}
	return root;
}

int cmp(Node a, Node b) {
	double ea = (double)a.area / a.count;
	double eb = (double)b.area / b.count;
	return ea != eb ? ea > eb:a.id < b.id;
}

int Union(int a, int b) {
	int fa = findFather(a);
	int fb = findFather(b);
	if (fa != fb)father[max(fa, fb)] = min(fa, fb);
	return 0;
}

int main() {
	for (int i = 0; i < maxn; i++)father[i] = i;
	int a, b, c, d, e, f, g, n;
	scanf("%d", &n);
	for (int i = 0; i < n; i++) {
		scanf("%d%d%d%d", &a, &b, &c, &d);
		if (b != -1)Union(a, b);
		if (c != -1)Union(a, c);
		for (int j = 0; j < d; j++) {
			scanf("%d", &e);
			Union(a, e);
		}
		scanf("%d %d", &vec[a].set, &vec[a].area);
	}
	for (int i = 0; i < maxn; i++) {
		int r = findFather(i);
		roots[r].id = r;
		roots[r].count++;
		roots[r].set += vec[i].set;
		roots[r].area += vec[i].area;
	}
	for (int i = 0; i < maxn; i++) {
		if (roots[i].area > 0) ans.push_back(roots[i]);
	}
	sort(ans.begin(), ans.end(), cmp);
	printf("%d\n", ans.size());
	for (auto x : ans) {
		printf("%04d %d %.3lf %.3lf\n", x.id, x.count, (double)x.set / x.count, (double)x.area / x.count);
	}
	return 0;
}
使用并查集统计图中的连通分量个数是一种高效且常用的方法。并查集通过维护一组元素的动态集合,支持快速的合并操作查询操作,能够有效地判断图中节点之间的连通性。 ### 实现思路 1. **初始化**:为图中的每个节点创建一个父节点数组 `parent`,初始时每个节点的父节点指向自己,表示每个节点独立为一个集合。 2. **合并操作**:遍历图中的所有边,对于每条边连接的两个节点,找到它们所属的集合(通过查找父节点),如果它们属于不同的集合,则将这两个集合合并。 3. **统计连通分量**:在所有合并操作完成后,遍历父节点数组,统计有多少个节点的父节点仍然指向自己。这些节点是各自集合的根节点,代表图中的连通分量数量。 ### 路径压缩优化 在查找父节点时,可以使用路径压缩技术优化查找效率。路径压缩通过将查找路径上的所有节点直接指向根节点,减少后续查找的开销。例如,查找函数 `find` 的实现如下: ```cpp int find(int x) { if (parent[x] != x) { parent[x] = find(parent[x]); // 路径压缩 } return parent[x]; } ``` ### 合并函数 合并两个集合时,需要找到它们的根节点,然后将其中一个根节点连接到另一个根节点上。合并函数 `union` 的实现如下: ```cpp void unite(int x, int y) { int rootX = find(x); int rootY = find(y); if (rootX != rootY) { parent[rootX] = rootY; // 将 x 的根节点连接到 y 的根节点上 } } ``` ### 统计连通分量 在完成所有边的合并操作后,遍历父节点数组,统计根节点的数量。每个根节点代表一个连通分量: ```cpp int countComponents(int n, vector<vector<int>>& edges) { // 初始化父节点数组 for (int i = 0; i < n; ++i) { parent[i] = i; } // 遍历所有边并合并 for (const auto& edge : edges) { unite(edge[0], edge[1]); } // 统计根节点的数量 int count = 0; for (int i = 0; i < n; ++i) { if (find(i) == i) { count++; } } return count; } ``` ### 示例代码 以下是一个完整的示例代码,展示如何使用并查集统计图中的连通分量数量: ```cpp #include <vector> using namespace std; class UnionFind { private: vector<int> parent; public: UnionFind(int n) { parent.resize(n); for (int i = 0; i < n; ++i) { parent[i] = i; // 初始化父节点数组 } } int find(int x) { if (parent[x] != x) { parent[x] = find(parent[x]); // 路径压缩 } return parent[x]; } void unite(int x, int y) { int rootX = find(x); int rootY = find(y); if (rootX != rootY) { parent[rootX] = rootY; // 合并集合 } } int countComponents(int n, vector<vector<int>>& edges) { // 遍历所有边并合并 for (const auto& edge : edges) { unite(edge[0], edge[1]); } // 统计根节点的数量 int count = 0; for (int i = 0; i < n; ++i) { if (find(i) == i) { count++; } } return count; } }; ``` ### 时间复杂度分析 - **初始化**:时间复杂度为 $O(n)$,其中 $n$ 是图中的节点数。 - **合并操作**:每次合并操作的时间复杂度为 $O(\alpha(n))$,其中 $\alpha(n)$ 是阿克曼函数的反函数,增长非常缓慢,可以认为是常数时间。 - **统计连通分量**:时间复杂度为 $O(n)$。 总体时间复杂度为 $O(n + m \cdot \alpha(n))$,其中 $m$ 是图中的边数。由于 $\alpha(n)$ 增长非常缓慢,因此并查集算法在实际应用中非常高效。 ### 相关问题 1. 如何优化并查集的查找效率? 2. 并查集在社交网络分析中的具体应用场景有哪些? 3. 如何使用并查集解决动态连通性问题? 4. 并查集与其他数据结构(如DFS/BFS)在连通分量统计中的优劣对比是什么? 5. 如何在并查集中实现按秩合并?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ProfSnail

谢谢老哥嗷

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值