算法------(11)并查集

news2024/11/20 18:35:58

例题:

(1)Acwing 836.合并集合

        并查集就是把每一个集合看成一棵树,记录每个节点的父节点。合并集合就是把一棵树变成另一棵树的子树,即把一棵树的父节点变为另一棵树的父节点的儿子。查询是否在同一集合就是看他们的根节点是否相同。在查找过程中可以路径加速,把每个点直接连到根节点。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5+10;
int p[N];
int find(int x){
    if(p[x]!=x) p[x] = find(p[x]);
    return p[x];
}
int main()
{
    int n,m;
    scanf("%d%d", &n, &m);
    for(int i = 1;i<=n;i++){
        p[i] = i;
    }
    for(int i = 0;i<m;i++){
        char op[2];
        int x,y;
        scanf("%s%d%d",op,&x,&y);
        if(op[0] == 'M'){
            p[find(x)] = find(y);
        }
        else{
            if(find(x)==find(y)) printf("Yes\n");
            else printf("No\n");
        }
    }
    return 0;
}

(2) Acwing 837.连通块中点的数量

         并查集板子题,把每个联通块当成一棵树,连边操作就等于将联通块合并,查询是否在同一个联通块中就等于查询其根节点是否相同,询问数量需要额外维护一个size数组,每次合并时根节点的size要加上被合并的根节点的size。如果合并时两个节点的根节点相同则无需合并,否则size会加倍。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5+10;
int sz[N],p[N];
int find(int x){
    if(p[x]!=x) p[x] = find(p[x]);
    return p[x];
}
int main()
{
    int n,m;
    scanf("%d%d", &n, &m);
    for(int i = 1;i<=n;i++){
       p[i] = i;
       sz[i] = 1;
    }
    for(int i = 0;i<m;i++){
        char op[2];
        int x,y;
        scanf("%s",op);
        if(op[0] == 'C'){
            scanf("%d%d", &x, &y);
            if(find(x)!=find(y)){
                sz[find(x)] += sz[find(y)];
                p[find(y)] = find(x);
            }
        }
        else if(op[1] == '1'){
            scanf("%d%d", &x, &y);
            if(find(x) == find(y)) printf("Yes\n");
            else printf("No\n");
        }
        else{
            scanf("%d", &x);
            printf("%d\n",sz[find(x)]);
        }
    }
    return 0;
}

(3)AcWing 240. 食物链

        由于是一个环形食物链,因此可以使用带权并查集,记录每个节点到根节点的距离,通过%3所得的数判断两者间的关系。具体代码有注释。 

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5+10;
int d[N],p[N];
int find(int x){
    if(p[x]!=x){
        int t = find(p[x]);//把该点的父节点以上的所有节点全部连到根节点
        d[x] += d[p[x]];//此时d[p[x]]就是p[x]到根节点的距离
        p[x] = t;//p[x]连到根节点
    }
    return p[x];
}
int main()
{
    int n,m,res = 0;
    scanf("%d%d", &n, &m);
    for(int i = 1;i<=n;i++){
        p[i] = i;
    }
    while (m -- ){
        int s,x,y;
        scanf("%d%d%d",&s, &x, &y);
        if(x>n||y>n){
            res++;
            continue;
        }
        else if(s == 1){
            int px = find(x),py = find(y);
            if(px == py){
                if((d[x]-d[y])%3) res++;//同类则模0
                continue;
            }
            else{
                p[px] = py;
                d[px] = d[y] - d[x];
            }
        }
        else{
            int px = find(x),py = find(y);
            if(px == py){
                if((d[x]-d[y]-1)%3) res++;//可能存在一正一负因此不能==1
                continue;
            }
            else{
                p[px] = py;
                d[px] = d[y] - d[x] + 1;
            }
        }
    }
    printf("%d",res);
    return 0;
}

练习:(1)Leetcode 128.最长相关序列

         把数组里的每一个数看做一个节点,而每个节点可以与权值比自己本身权值小1的节点相连。对于数组中的每个数,如果权值比他小1的数在哈希表中存在并且两者不具有相同的根结点时,由于数组中不存在重复元素,且每次只会把大的数接在小的数后面,因此两者集合必然是连续的。所以先进行去重然后开始建树,由于存在负数因此利用哈希表储存每个数的下标防止访问越界。问题可以转化为最大的联通块中点的数量。

class Solution {
public:
    unordered_map<int,int> x;
    int p[100010],sz[100010];
    int find(int x){
        if(p[x]!=x) p[x] = find(p[x]);
        return p[x];
    }
    int longestConsecutive(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        nums.erase(unique(nums.begin(),nums.end()),nums.end());
        int n = nums.size();
        for(int i = 1;i<=n;i++){
            x[nums[i-1]] = i;
            p[i] = i;
            sz[i] = 1;
        }
        for(int i = 0;i<n;i++){
            if(x[nums[i]-1]&&find(x[nums[i]-1]) != find(x[nums[i]]) ){
                sz[find(x[nums[i]-1])] += sz[find(x[nums[i]])];
                p[find(x[nums[i]])] = find(x[nums[i]-1]);
            }
        }
        int ans = 0;
        for(int i = 0;i<n;i++){
            if(p[x[nums[i]]] == x[nums[i]]) ans = max(ans,sz[x[nums[i]]]);
        }
        return ans;
    }
};

(2)Leetcode 684.冗余连接

        没做出来。。

        遍历每条边,如果该边的两个端点属于一个集合内,那么就说明这条边导致了一个环的构成。返回这条边。否则把两个端点加到同一个集合内。由于题目规定只会多出一条边(只会存在一个环),因此出现的这条边一定是构成环的最后一条边,因为假如后面还存在能构成环的边则不满足只有一个环的条件,因此这条边就是构成环的最后一条边。

class Solution {
public:
    int p[1010];
    int find(int x){
        if(p[x]!=x) p[x] = find(p[x]);
        return p[x];
    }
    vector<int> findRedundantConnection(vector<vector<int>>& edges) {
        int n = edges.size();
        for(int i = 1;i<=n;i++) p[i] = i;
        for(auto c:edges){
            int a = find(c[0]),b = find(c[1]);
            if(a==b) return{c[0],c[1]};
            else{
                p[a] = b;
            }
        }
        return {0,0};
    }
};

(3)Leetcode 1202.交换字符串中的元素

        并查集应该不是最佳解。。不过没想出来啥别的方法。。

        第一步:把所有能交换的位置构成一个集合。第二步:遍历字符串,把每个字母加入其根节点映射的优先队列(可以自动按字典序排序)第三步:遍历字符串,用一个空字符串记录结果。在每个字母对应的位置放上其根节点对应的优先队列中第一个字母(按字典序最小的字母),然后该元素出队。

class Solution {
public:
    int p[100010];
    int find(int x){
        if(p[x]!=x) p[x] = find(p[x]);
        return p[x];
    }
    string smallestStringWithSwaps(string s, vector<vector<int>>& pairs) {
        unordered_map<int,priority_queue<int,vector<int>,greater<int>>> x;
        int n = s.length();
        for(int i = 0;i<n;i++) p[i] = i;
        for(auto c:pairs){
            int a = find(c[0]),b = find(c[1]);
            if(a!=b) p[a] = b;
        }   
        for(int i = 0;i<n;i++) x[find(i)].push((int)s[i]);
        string res = "";
        for(int i = 0;i<n;i++){
            char ch = (char)x[find(i)].top();
            x[find(i)].pop();
            res+=ch;
        }
        return res;
    }
};

(4)Leetcode 886.可能的二分法

        判断二分图的最好方法并不是并查集,但是。。用并查集没做出来。。

        把每一个人的关系不好的人放入一个集合中,如果这个集合的人里面有人跟该人的根节点相同说明其属于同一个集合,不成立,反之则把他们加入同一个集合。

class Solution {
public:
    unordered_map<int,vector<int>> x;
    int p[2010];
    int find(int x){
        if(p[x]!=x) p[x] = find(p[x]);
        return p[x];
    }
    bool possibleBipartition(int n, vector<vector<int>>& dislikes) {
        if(!dislikes.size()) return true;
        for(auto c:dislikes){
            x[c[0]].push_back(c[1]);
            x[c[1]].push_back(c[0]);
        }
        for(int i = 1;i<=n;i++) p[i] = i;
        for(int i = 1;i<=n;i++){
            if(x[i].empty()) continue;
            int a = find(i),b = find(x[i][0]);
            for(auto c:x[i]){
                if(find(c) == a) return false;
                p[find(c)] = b;
            }
        }
        return true;
    }
};

(5) P8686 [蓝桥杯 2019 省 A] 修改数组

         。。。其实还是没太理解。。

        首先把每一个数的根节点设为其本身,然后对于每次输入的数,如果其根节点为本身,说明其第一次出现,因此把根节点更新为原先的根节点+1,否则查找其根节点(由于每次都加1,因此 从 其本身 到 其根节点-1 都已经出现过),然后输出根节点并且把 根节点+1 作为根节点的新父节点(这个父节点有可能还会有父节点,但并不影响,可以理解为两条链相连成为一条更长的新链)

        初始化时要把后面的节点一起初始化,否则更新时由于后续某些节点的根节点变为0就会出错。。

#include <iostream>
using namespace std;
const int N = 100010;
typedef long long ll;
ll p[N];
ll find(ll x){
	if(p[x]!=x) p[x] = find(p[x]);
	return p[x];
} 
int main(int argc, char** argv) {
	ll n;
	scanf("%d",&n);
	for(int i = 1;i<=100010;i++) p[i] = i;
	for(int i = 1;i<=n;i++){
		ll x;
		scanf("%lld",&x);
		printf("%lld ",find(x));
		p[find(x)] = find(x) + 1;
	}
	return 0;
}

(7)洛谷 P1111 修复公路

        。。很烦。。        

        联通块的个数为1时就能完全通车。首先按照时间排序(分清路的条数和村庄的个数!!),然后遍历每一条路,假如两个路边上的村庄共用一个根节点则无视,否则联通块数-1,把两个村庄联通。每次判断所有联通块是否只剩一个,如果是则输出当前时间戳,如果最后还剩不止一个联通块则输出-1。这里判断联通块个数不需要每一次都遍历,只需要维护联通块个数即可(因为一开始每一个村庄就是一个联通块)

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010,M = 100010;
struct Node{
	int x,y,t; 
	bool operator< (Node &a){
		return t < a.t;
	}
}road[M];
int p[N];
int find(int x){
	if(p[x]!=x) p[x] = find(p[x]);
	return p[x];
}
int main(int argc, char** argv) {
	int n,m;
	scanf("%d%d",&n,&m);
	int res = n; 
	for(int i = 1;i<=n;i++) p[i] = i;
	for(int i = 0;i<m;i++){
		scanf("%d%d%d",&road[i].x,&road[i].y,&road[i].t);
	}
	sort(road,road+m);
	bool flag = 0;
	for(int i = 0;i<m;i++){
		int a = find(road[i].x),b = find(road[i].y),c = road[i].t;
		if(a == b) continue;
		else{
			res--;
			if(res==1){
				printf("%d",c);
				flag = 1;
				break;
			}
			p[a] = b;
		}
	}
	if(!flag) printf("-1");
	return 0;
}

(8)洛谷 P1455 搭配购买

        一个缝合题,01背包加并查集。。01背包写的很差,还是不太会用滚动数组。。后面再补一下吧。。

        用并查集把多个商品合成为一个商品,根节点代表商品本身。01背包不用滚动数组会MLE。更新时注意是更新根节点的价格和价值(因为是连接根节点)。

#include <iostream>
using namespace std;
const int N = 1e4+10;
int w[N],v[N],p[N],f[N];
struct Node{
	int w,v;
}shop[N];
int find(int x){
	if(p[x]!=x) p[x] = find(p[x]);
	return p[x];
}
int main(int argc, char** argv) {
	int n,m,w1;
	scanf("%d%d%d",&n,&m,&w1);
	for(int i = 1;i<=n;i++){
		scanf("%d%d",&w[i],&v[i]);
		p[i] = i;
	}
	for(int i = 0;i<m;i++){
		int a,b;
		scanf("%d%d",&a,&b);
		a = find(a),b = find(b);
		if(a==b) continue;
		p[a] = b;
		w[b] += w[a];
		v[b] += v[a];
	}
	int cnt = 1;
	for(int i = 1;i<=n;i++){
		if(p[i]!=i) continue;
		shop[cnt].w = w[i];
		shop[cnt].v = v[i];
		cnt++;
 	}
	for(int i = 1;i<=n;i++){
		for(int j = w1;j>=shop[i].w;j--){
			f[j] = max(f[j],f[j-shop[i].w] + shop[i].v);
		}
	}
	printf("%d",f[w1]);
	return 0;
}

(9)P3420 SKA-Piggy Banks

        虽然做出来了。。但是感觉还是搞不太明白。。

        这道题之所以可以用并查集的原因并不是因为罐子之间存在钥匙联系,而是一个罐子只有一个钥匙且在另一个罐子里,但一个罐子可以装多把钥匙。因此我们每次把钥匙对应的罐子连在存放钥匙的罐子后面,则打开根节点的罐子就可以打开所有罐子。因此一个集合内的罐子就一定只需要打开一个罐子,题目也就改为求联通块的数目。因此这道题用并查集可以做的原因其实是每个钥匙唯一且对应了一个罐子。假如题目改为第i个数代表第i个罐子存放了第ai个罐子的钥匙,这道题就不能用并查集做了。。https://www.luogu.com.cn/discuss/684322icon-default.png?t=N7T8https://www.luogu.com.cn/discuss/684322        这是比较专业的解答。。。我只能用自己的角度去解释。。解释的不太好。。

(10)P1196 [NOI2002] 银河英雄传说

        。。确实是挺难的。。

        为了查询两个战舰之间的战舰数目,我们维护每一个战舰到根节点的距离,这样两个战舰之间的战舰数目就是到根节点的距离差-1。而由于我们维护到根节点的距离还需要知道当前集合内的战舰数量,因此还需要维护一个数组用来记录每个集合(根节点下)的战舰数。当然,由于每个节点都没法在合并时就被更新,因此我们可以在find函数时对每个节点进行更新。 

#include <iostream>
using namespace std;
const int N = 30010;
int p[N],dis[N],num[N];
int find(int x){
	if(p[x]!=x){
		int k = p[x]; 
		p[x] = find(p[x]);
		dis[x] += dis[k];
		num[x] = num[p[x]];
	}
	return p[x];
}
int main(int argc, char** argv) {
	int t;
	scanf("%d",&t);
	for(int i = 1;i<=30000;i++){
		p[i] = i;
		num[i] = 1;
	}
	while(t--){
		char op[2];
		scanf("%s",op);
		int x,y;
		scanf("%d%d",&x,&y);
		if(op[0] == 'M'){
			x = find(x);
			y = find(y);
			if(x!=y){
				p[x] = y;
				dis[x] = num[y];
				num[y] += num[x];
				num[x] = num[y];
			}
		}
		else{
			if(find(x) == find(y)) printf("%d\n",abs(dis[x] - dis[y])-1);
			else printf("-1\n");
		}
	}
	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1442418.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

生成式人工智能攻击的一年:2024

趋势科技最近公布了其关于预期最危险威胁的年度研究数据。生成人工智能的广泛可用性和质量将是网络钓鱼攻击和策略发生巨大变化的主要原因。 趋势科技宣布推出“关键可扩展性”&#xff0c;这是著名年度研究的新版本&#xff0c;该研究分析了安全形势并提出了全年将肆虐的网络…

CSGO游戏搬砖项目靠谱吗?是不是骗人的

很多地方都在大肆宣扬说CSGO游戏搬砖项目有二三十个点的利润&#xff0c;但我觉得他们看待问题太片面了&#xff0c;没有从全局上去分析这个项目。 这些人为了能割到小白的韭菜真是无所不用其极&#xff0c;什么牛都能吹得出来&#xff01;至少要实事求是吧&#xff0c;这不睁…

C语言----内存函数

内存函数主要用于动态分配和管理内存&#xff0c;它直接从指针的方位上进行操作&#xff0c;可以实现字节单位的操作。 其包含的头文件都是&#xff1a;string.h memcpy copy block of memory的缩写----拷贝内存块 格式&#xff1a; void *memcpy(void *dest, const void …

政安晨:政安晨:机器学习快速入门(三){pandas与scikit-learn} {模型验证及欠拟合与过拟合}

这一篇中&#xff0c;咱们使用Pandas与Scikit-liarn工具进行一下模型验证&#xff0c;之后再顺势了解一些过拟合与欠拟合&#xff0c;这是您逐渐深入机器学习的开始&#xff01; 模型验证 评估您的模型性能&#xff0c;以便测试和比较其他选择。 在上一篇中&#xff0c;您已经…

Dynamo批量处理多个Revit文件?

Hello大家好&#xff01;我是九哥~ 最近很多小伙伴都在咨询Dynamo如何批量处理多个Revit文件&#xff0c;之前写过一篇《Dynamo批量修改多文件项目基点参数》&#xff0c;利用的是后台打开Revit的方式&#xff0c;可以实现一些批量操作的功能。 但是这个方法&#xff0c;对于一…

前端JavaScript篇之实现call、apply 及 bind 函数

目录 实现call、apply 及 bind 函数1. 实现call函数2. 实现apply函数3. 实现bind函数 实现call、apply 及 bind 函数 call、apply和bind函数都是用于改变函数中this指向的方法。它们的作用都是使函数能够在不同的对象上下文中运行。call方法和apply方法的作用类似&#xff0c;…

Web课程学习笔记--JavaScript数据类型和数据结构

JavaScript 数据类型和数据结构 编程语言都具有内建的数据结构&#xff0c;但各种编程语言的数据结构常有不同之处。本文试图列出 JavaScript 语言中内建的数据结构及其属性&#xff0c;它们可以用来构建其他的数据结构&#xff1b;同时尽可能的描述与其他语言的不同之处 动态…

学习通考试怎么搜题找答案? #学习方法#微信#其他

大学生必备的做题、搜题神器&#xff0c;收录上万本教材辅助书籍&#xff0c;像什么高数、物理、计算机、外语等都有&#xff0c;资源十分丰富。 1.菜鸟教程 菜鸟教程是一个完全免费的编程学习软件。 它免费提供了HTML / CSS 、JavaScript 、服务端、移动端、XML 教程、http…

HCIA--NAT实验

1. 划分网段&#xff0c;配置接口IP地址&#xff0c;内网启用OSPF协议&#xff0c;并配置一对一的NAT&#xff1a; AR1配置&#xff1a; [Huawei]int g0/0/0 [Huawei-GigabitEthernet0/0/0]ip add 10.1.1.1 24 [Huawei-GigabitEthernet0/0/0]int g0/0/1 [Huawei-GigabitEther…

eeeeeeeeeeeeeeeeee

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起探讨和分享Linux C/C/Python/Shell编程、机器人技术、机器学习、机器视觉、嵌入式AI相关领域的知识和技术。 磁盘满的本质分析 专栏&#xff1a;《Linux从小白到大神》 | 系统学习Linux开发、VIM/GCC/GDB/Make工具…

特征工程:数据平衡

目录 一、前言 二、正文 Ⅰ.基于过采样算法 Ⅱ.基于欠采样算法 Ⅲ..基于过采样和欠采样的综合算法 三、结语 一、前言 大多数情况下&#xff0c;使用的数据集是不完美的&#xff0c;会出现各种各样的问题&#xff0c;尤其针对分类问题的时候&#xff0c;会出现类别不平衡的…

《MySQL 简易速速上手小册》第5章:高可用性和灾难恢复(2024 最新版)

文章目录 5.1 构建高可用性 MySQL 解决方案5.1.1 基础知识5.1.2 重点案例&#xff1a;使用 Python 构建高可用性的电子商务平台数据库5.1.3 拓展案例 5.2 数据备份策略和工具5.2.1 基础知识5.2.2 重点案例&#xff1a;使用 Python 实现 MySQL 定期备份5.2.3 拓展案例 5.3 灾难恢…

【5G NR】【一文读懂系列】移动通讯中使用的信道编解码技术-卷积码原理

目录 一、引言 二、卷积编码的发展历史 2.1 卷积码的起源 2.2 主要发展阶段 2.3 重要里程碑 三、卷积编码的基本概念 3.1 基本定义 3.2 编码器框图 3.3 编码多项式 3.4 网格图(Trellis)描述 四、MATLAB示例 一、引言 卷积编码&#xff0c;作为数字通信领域中的一项…

redis双写一致

redis双写一致&#xff0c;指的是redis缓存与mysql数据同步 双写一致常见方案有很多&#xff1a; 同步双写&#xff1a;更新完mysql后立即同时更新redis mq同步&#xff1a;程序在更新完mysql后&#xff0c;投递消息到中间键mq&#xff0c;一个程序监听mq&#xff0c;获得消…

用EXCEL从地址(上海)中提取各区(浦东新区等区)信息

背景&#xff1a; 朋友工作需要经常用EXCEL把各上海用户收货地址中的区提取出来&#xff0c;之前一直手动处理&#xff0c;希望我帮忙用EXCEL公式直接提取处理。 数据样式&#xff1a; 中国上海市浦东新区A小区 上海徐汇区B小区 中国&#xff0c;上海&#xff0c;浦东新区&a…

Android:Volley框架使用

3.15 Volley框架使用 Volley框架主要作为网络请求,图片加载工具。当应用数据量小、网络请求频繁,可以使用Volley框架。 框架Github地址:https://github.com/google/volley Volley框架的简单使用,创建项目Pro_VolleyDemo。将Github上下载Volley框架源代码,volley-master.zi…

【05】C++ 内存管理

文章目录 &#x1f308; Ⅰ C 内存分布&#x1f308; Ⅱ C 内存管理方式1. new 和 delete 操作内置类型2. new 和 delete 操作自定义类型 &#x1f308; Ⅲ operator new 和 operator delete&#x1f308; Ⅳ new 和 delete 的实现原理1. 内置数据类型2. 自定义数据类型 &#…

leetcode 3027. 人员站位的方案数 II【离散化前缀和+枚举】

原题链接&#xff1a;3027. 人员站位的方案数 II 题目描述&#xff1a; 给你一个 n x 2 的二维数组 points &#xff0c;它表示二维平面上的一些点坐标&#xff0c;其中 points[i] [xi, yi] 。 我们定义 x 轴的正方向为 右 &#xff08;x 轴递增的方向&#xff09;&#x…

架构之模板方法等模式的使用

目录 一、程序编写背景 二、编程思路讲解 - 类图 - 实现逻辑 - 工厂模式 - 模板方法模式 接口类&#xff08;代码&#xff09;抽象类&#xff08;代码&#xff09;具体实现类&#xff08;代码&#xff09;工厂类&#xff08;代码&#xff09;注册类&#xff08;代码&…

租用海外服务器丢包是什么情况?

在当今的互联网时代&#xff0c;海外服务器租用已经成为了许多企业和个人的选择。然而&#xff0c;在使用海外服务器的过程中&#xff0c;有时会出现丢包的情况&#xff0c;这给用户带来了不小的困扰。那么&#xff0c;租用海外服务器丢包是什么情况呢&#xff1f;本文将对此进…