数据结构-并查集专题(2)

一、前言

接(1)完成剩余题目和了解并查集运用求解最小生成树的Kruskal算法

二、专题训练

2.1 题目总览

前四题见(1)

2.2 1568: 并查集-家谱

思路

首先这个题目的描述就有问题,它说每一组的父子关系由两行组成,那样例的第3到第5行你要怎么解释,麻了。这里应该是#开头的是父亲,下面紧跟着的若干行+开头的都是他的儿子。而且这个题也是不适合用我们的模板去解的,因为它需要从字符串映射到字符串,当然你也可以给字符串编个序号这样也可以实现,我这里给出另一种解法,我们用哈希表来实现字符串到字符串的映射。其余的就是并查集的基本操作了

参考代码

cpp 复制代码
#include <bits/stdc++.h>
#include <functional>
using i64 = long long;

using pss = std::pair<std::string,std::string>;

int main() {
    std::cin.tie(nullptr)->sync_with_stdio(false);

    std::vector<pss> v;
    pss pair = {"",""};
    std::vector<std::string> query;
    std::string line;
    std::unordered_map<std::string,std::string> fa;
    std::string father,son;

    using Find = std::function<std::string(std::string)>;
    Find find = [&](std::string x)  {
        if(x!=fa[x]) fa[x] = find(fa[x]);
        return fa[x];
    };

    while(std::getline(std::cin,line)) {
        if(line[0]=='#') {
            father = line.substr(1);
            if(fa[father]=="") fa[father] = father;
        }else if(line[0]=='+') {
            son = line.substr(1);
            if(fa[son]=="") {
                fa[son] = find(father);
            }
        }else if(line[0]=='?') {
            query.emplace_back(line.substr(1));
        }else {//line[0]=='$'
            break;
        }
    }

    for(auto &str:query) {
        std::cout << str << ' ' << find(str) << '\n';
    }

    return 0;
}

2.3 1836: 并查集-格子游戏

思路

对于并查集来说,一个节点的值最常见的是一个int,上一题节点的值是一个字符串,这道题节点的值是一个坐标点,当然你可以像上一题一样,写一个坐标的结构体,然后用哈希表将坐标映射到坐标,我这里采用将坐标变换成一个int,然后每次操作时判断,要连接的两个点是否已经在同一个并查集中了,如果是则输出当前的步数,如果不是则将两个节点进行合并,执行到结束,如果游戏仍然没有结束则输出"draw"

参考代码

注意并查集初始化的时候,空间要开大一些

cpp 复制代码
#include <bits/stdc++.h>
using i64 = long long;

struct DisjointSet {
    int _n;
    std::vector<int> _fa,_size;
    DisjointSet(){}
    DisjointSet(int n){
        init(n);
    }
    void init(int n) {
        _fa.resize(n);
        std::iota(_fa.begin(),_fa.end(),0);
        _size.assign(n,1);
    }
    int find(int x) {
        if(x!=_fa[x]) {
            _fa[x] = find(_fa[x]);
        }
        return _fa[x];
    }
    bool same(int x,int y) {
        return find(x)==find(y);
    }
    bool merge(int x,int y) {
        int fx = find(x);
        int fy = find(y);
        if(fx!=fy) {
            _size[fx]+=_size[fy];
            _fa[fy] = fx;
            return true;
        }
        return false;
    }
};

int main() {
    std::cin.tie(nullptr)->sync_with_stdio(false);

    int n,m;
    std::cin >> n >> m;
    DisjointSet disjointSet = DisjointSet((n+5)*(n+2));
    
    for(int i = 0;i<m;i++) {
        int x1,y1,x2,y2;
        std::cin >> x1 >> y1;
        std::string op;
        std::cin >> op;
        if(op=="D") {
            x2 = x1+1,y2 = y1;
        }else {
            x2 = x1,y2 = y1+1;
        }
        int t1 = n*(x1-1)+y1-1,t2 = n*(x2-1)+y2-1;

        if(!disjointSet.merge(t1,t2)) {
            std::cout << i+1 << '\n';
            return 0;
        }
    }
    std::cout << "draw" << '\n';
    
    return 0;
}

2.4 1837: 并查集-亲戚

思路

此题思路很一般,正常做就行,但是因为数据量的限制,我们写并查集的时候一定要做路径压缩,不然会TLE,直接用我们的模板就行

参考代码

cpp 复制代码
#include <bits/stdc++.h>
using i64 = long long;

struct DisjointSet {
    int _n;
    std::vector<int> _fa,_size;
    DisjointSet(){}
    DisjointSet(int n){
        init(n);
    }
    void init(int n) {
        _fa.resize(n);
        std::iota(_fa.begin(),_fa.end(),0);
        _size.assign(n,1);
    }
    int find(int x) {
        if(x!=_fa[x]) {
            _fa[x] = find(_fa[x]);
        }
        return _fa[x];
    }
    bool same(int x,int y) {
        return find(x)==find(y);
    }
    bool merge(int x,int y) {
        int fx = find(x);
        int fy = find(y);
        if(fx!=fy) {
            _size[fx]+=_size[fy];
            _fa[fy] = fx;
            return true;
        }
        return false;
    }
};

int main() {
    std::cin.tie(nullptr)->sync_with_stdio(false);

    int n,m;
    std::cin >> n >> m;
    DisjointSet disjointSet = DisjointSet(n+1);

    for(int i = 0;i<m;i++) {
        int x,y;
        std::cin >> x >> y;
        disjointSet.merge(x,y);
    }
    int q;
    std::cin >> q;
    while(q--) {
        int x,y;
        std::cin >> x >> y;
        std::cout << (disjointSet.same(x,y)?"Yes\n":"No\n");
    }
    
    return 0;
}

三、并查集的运用

3.1 最小生成树

最小生成树是一个现实中经常遇到的问题,各个城镇直接修路、修桥,我们力争使用的材料或者经费最少,就需要求出最小生成树

3.2 Prim算法(基于贪心算法,与求最短路的Dijkstra算法类似)

3.3 Kruskal算法(也是基于贪心算法,需要用到并查集)

把带权边按照权值升序排序,开始循环找当前权值最小的边,如果两个节点不在同一个并查集中,则将它们合并,如果在同一个并查集中,则继续找下一条边。循环结束,如果有n-1条边,则成功找到最小生成树(最小生成树不唯一,但最后的权值和最小是唯一的),如果循环结束,找不到n-1条边,则不存在最小生成树

3.3.1 例题1 AcWing 859. Kruskal算法求最小生成树

思路

没啥好说的,是一个模板题

参考代码
cpp 复制代码
#include <bits/stdc++.h>
using i64 = long long;

struct Edge {
    int _x, _y, _w;
    Edge(int x, int y, int w) : _x(x), _y(y), _w(w) {}
    bool operator<(const Edge& edge2) const {
        return _w < edge2._w;
    }
};

struct DisjointSet {
    int _n;
    std::vector<int> _fa,_size;
    DisjointSet(){}
    DisjointSet(int n){
        init(n);
    }
    void init(int n) {
        _fa.resize(n);
        std::iota(_fa.begin(),_fa.end(),0);
        _size.assign(n,1);
    }
    int find(int x) {
        if(x!=_fa[x]) {
            _fa[x] = find(_fa[x]);
        }
        return _fa[x];
    }
    bool same(int x,int y) {
        return find(x)==find(y);
    }
    bool merge(int x,int y) {
        int fx = find(x);
        int fy = find(y);
        if(fx!=fy) {
            _size[fx]+=_size[fy];
            _fa[fy] = fx;
            return true;
        }
        return false;
    }
};

int main() {
    std::cin.tie(nullptr)->sync_with_stdio(false);

    int n, m;
    std::cin >> n >> m;
    std::vector<Edge> edges;
    DisjointSet disjointSet(n);

    for (int i = 0; i < m; ++i) {
        int x, y, w;
        std::cin >> x >> y >> w;
        edges.emplace_back(x, y, w);
    }
    std::sort(edges.begin(), edges.end());

    int ans = 0, cnt = 0;
    for (const auto& edge : edges) {
        if (disjointSet.merge(edge._x, edge._y)) {
            ans += edge._w;
            ++cnt;
            if (cnt == n - 1) break;
        }
    }
    std::cout << (cnt < n - 1 ? "impossible" : std::to_string(ans)) << '\n';

    return 0;
}

3.3.2 例题2 最小生成树-最优布线问题

思路

题目的输入是邻接矩阵的形式,我们把它转换成边存储,然后再用Kruskal算法即可,当然这个题用Prim算法更合适

参考代码1(Kruskal算法)
cpp 复制代码
#include <bits/stdc++.h>
using i64 = long long;

struct Edge {
    int _x,_y,_w;
    Edge(int x,int y,int w):_x(x),_y(y),_w(w){}
    bool operator < (const Edge& edge2) const {
        return _w<edge2._w;
    }
};

struct DisjointSet {
    int _n;
    std::vector<int> _fa,_size;
    DisjointSet(){}
    DisjointSet(int n){
        init(n);
    }
    void init(int n) {
        _fa.resize(n);
        std::iota(_fa.begin(),_fa.end(),0);
        _size.assign(n,1);
    }
    int find(int x) {
        if(x!=_fa[x]) {
            _fa[x] = find(_fa[x]);
        }
        return _fa[x];
    }
    bool same(int x,int y) {
        return find(x)==find(y);
    }
    bool merge(int x,int y) {
        int fx = find(x);
        int fy = find(y);
        if(fx!=fy) {
            _size[fx]+=_size[fy];
            _fa[fy] = fx;
            return true;
        }
        return false;
    }
};

int main() {
    std::cin.tie(nullptr)->sync_with_stdio(false);

    int n;
    std::cin >> n;
    std::vector<Edge> edges;
    DisjointSet disjointSet = DisjointSet(n+1);

    for(int i = 1;i<=n;i++) {
        for(int j = 1;j<=n;j++) {
            int w;
            std::cin >> w;
            if(j>=i) {
                edges.emplace_back(i,j,w);
            }
        }
    }

    std::sort(edges.begin(),edges.end());

    int ans = 0;
    for(int i = 0;i<edges.size();i++) {
        int x = edges[i]._x,y = edges[i]._y,w = edges[i]._w;
        int fx = disjointSet.find(x),fy = disjointSet.find(y);
        if(disjointSet.merge(fx,fy)) {
            ans+=w;
        }
    }
    std::cout << ans << '\n';
    
    return 0;
}
参考代码2(Prim算法)
cpp 复制代码
#include <iostream>
#include <cstring>
#include <type_traits>

template<typename T1,typename T2>
typename std::common_type<T1,T2>::type min(T1 num1,T2 num2){
	return num1>num2?num2:num1;
}
int main(){
	std::cin.tie(nullptr)->sync_with_stdio(false);

	constexpr int MAX_N = 1e2+5,INF = 0x3f3f3f3f;
	int g[MAX_N][MAX_N];
	int dist[MAX_N];
	bool vis[MAX_N] {};

	int n;std::cin >> n;
	for(int i = 1;i<=n;++i){
		for(int j = 1;j<=n;++j){
			std::cin >> g[i][j];
		}
	}

	auto prim = [&]()->int{
		memset(dist,0x3f,sizeof dist);
		int res = 0;
		dist[1] = 0;
		for(int i = 0;i<n;++i){
			int t = -1;
			for(int j = 1;j<=n;++j){
				if(!vis[j]&&(t==-1||dist[j]<dist[t])) t = j;
			}
			if(dist[t]==INF) return INF;
			vis[t] = true;
			res+=dist[t];
			for(int j = 1;j<=n;++j) dist[j]=min(dist[j],g[t][j]);
		}
		return res;
	};

	std::cout << prim() << '\n';

	return 0;
}
相关推荐
理论最高的吻1 小时前
98. 验证二叉搜索树【 力扣(LeetCode) 】
数据结构·c++·算法·leetcode·职场和发展·二叉树·c
沈小农学编程1 小时前
【LeetCode面试150】——202快乐数
c++·python·算法·leetcode·面试·职场和发展
ZZZ_O^O1 小时前
【动态规划-卡特兰数——96.不同的二叉搜索树】
c++·学习·算法·leetcode·动态规划
一只小透明啊啊啊啊2 小时前
Leetcode100子串
算法
木向2 小时前
leetcode:114. 二叉树展开为链表
算法·leetcode·链表
sky_smile_Allen2 小时前
[C#] 关于数组的详细解释以及使用注意点
开发语言·算法·c#
希望有朝一日能如愿以偿2 小时前
力扣题解(新增道路查询后的最短距离II)
算法
我感觉。2 小时前
【机器学习chp6】对数几率回归
算法·机器学习·逻辑回归·分类模型·对数几率回归
无限大.3 小时前
力扣题解3248 矩阵中的蛇(简单)
算法·leetcode·矩阵
灼华十一3 小时前
算法编程题-排序
数据结构·算法·golang·排序算法