用并查集解决CSP合根植物

原题链接

CSP 合根植物

解题思路

搜索

刚开始看到题目就想当然的认为是一道BFS,把整个矩阵看成0,1阵,连通用1表示,谁知道我当时怎么想到,很明显这样不能区分不同的块,唯一的价值就是找了编号和行列号的关系吧。

// 编号为a,则:
// 行号为(a-1)/n
// 列号(a-1)%n

本来想了想以为不同的块用不同的数字表示也可以做,比如第一块用1 ,第二块都用2,但是并不能保证读入顺序,还是无法分辨出不同的块。

并查集

用并查集的话那个矩阵就用不到了,就当成是 m ∗ n m*n mn个个体,用father数组值表示某元素属于哪个集合,初始化为自身,即每个元素所处的块编号都为自身编号,然后在读入连通编号时,将这两个元素所在的集合合并为一个集合。

源代码

没什么用的BFS

就当自己复习复习BFS了。

// 合根植物 BFS,不能全部设为1,无法区分不同的连通块 
#include<iostream>
#include<queue>
using namespace std;
const int maxn = 1010;
int m, n;
struct Node{
	int x, y; // 坐标 
}temp; 
bool matrix[maxn][maxn]; // 0,1矩阵表示连通性 
bool inq[maxn][maxn]; // 各坐标位置的点是否已经入过队 

// 增量矩阵
int X[4] = {0, 0, -1, 1};  
int Y[4] = {-1, 1, 0, 0}; 

bool check(int x, int y){
	// 判断该位置点是否有必要访问
	if(x < 0 || x >= m || y < 0 || y >= n) // 越界 
		return false;
	if(inq[x][y]) // 已入队 
		return false;
	if(!matrix[x][y]) // 不连通 
		return false;
	return true; // 除此之外的点都需要访问 
}
void BFS(int x, int y){
	// 将(x, y)所在连通块都设置为已入队即Inq = true
	queue<Node> q; 
	Node node;
	node.x = x;
	node.y = y;
	q.push(node);
	inq[x][y] = true;
	while(!q.empty()){
		Node tmp = q.front();
		q.pop();
		for(int i=0; i<4; i++){
			int newX = tmp.x + X[i];
			int newY = tmp.y + Y[i];
			if(check(newX, newY)){
				temp.x = newX;
				temp.y = newY;
				q.push(temp);
				inq[newX][newY] = true;
			} 
		}
	}
	 
}
int main(){
	fill(matrix[0], matrix[0]+maxn*maxn, false) ; // 0表示不连通
	scanf("%d%d", &m, &n); // m行n列的矩阵
	int k;
	scanf("%d", &k);
	int a, b;
	for(int i=0; i<k; i++){
		scanf("%d%d", &a, &b);
		// 连通设为1 
		matrix[(a-1)/n][(a-1)%n] = 1;
		matrix[(b-1)/n][(b-1)%n] = 1;
	} 
	int res = 0; // 连通块的个数 
	for(int i=0; i<m; i++){
		for(int j=0; j<n; j++){
			if(matrix[i][j] &&!inq[i][j]){
				res++;
				BFS(i, j);
			}
			if(!matrix[i][j])
				res++; // 单独一个植物,没有和别的合根 
		}
	} 
	printf("%d\n", res);
	return 0;
} 

100分的并查集(有路径压缩,无按秩归并)

#include<iostream>
using namespace std;
const int maxn = 1000010;
int father[maxn]; // 表示各编号植物的父编号 
int m, n; // m行n列 
void init(int num){
	for(int i=0; i<=num; i++){
		father[i] = i; // 初始化父节点为自身 
	}
}
int find_father(int x){
	// 返回x的父亲结点,并且进行路径压缩
	int a = x;
	while(x != father[x]){
		x = father[x];
	} 
	// x已经为父节点,从a开始再走一遍进行路径压缩
	while(a != father[a]){
		int z = a;
		a = father[a];
		father[z] = x;
	} 
	return x; 
}
void Union(int a, int b){
	// 合并a,b所在的两个集合
	int faA = find_father(a);
	int faB = find_father(b);
	if(faA != faB){
		// TODO 这里应该实现按秩归并 
		father[faA] = faB;
	} 
}
int main(){
	scanf("%d%d", &m, &n);
	init(m*n); 
	int k;
	scanf("%d", &k);
	int a, b;
	for(int i=0; i<k; i++){
		scanf("%d%d", &a, &b);
		Union(a, b);
	} 
	int num = m*n; 
	int ans = 0;
	for(int i=1; i<=m*n; i++){
		if(father[i] == i){
			ans ++ ;
		}
	}
	printf("%d\n", ans);
	return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值