并查集

并查集(英文:Disjoint-set data structure,不交集数据结构)用于处理一些不交集(Disjoint sets,一系列没有重复元素的集合)的合并及查询问题

时间复杂度与空间复杂度

  • 时间复杂度: O(ɒ(n))
  • 空间复杂度: O(n)

优缺点

  • 优点:网络路由、符号计算、寄存器分配方面有优势 😉
    并查集应用

渗透

Coursera

核心代码

查询

查询某个元素属于哪个集合,通常是返回集合内的一个“代表元素”。这个操作是为了判断两个元素是否在同一个集合之中。

int Find(int x) { return x == f[x] ? x : f[x] = Find(f[x]); } 
/** 注意 返回 f[x] = Find(f[x]) ! */

合并

将两个集合合并为一个。

void Union(int x, int y) { f[Find(x)] = Find(y); }
//	int x_root = Find(x), y_root = Find(y);
//	if (x_root != y_root) f[x_root] = y_root;

添加

添加一个新集合,其中有一个新元素。添加操作不如查询和合并操作重要,常常被忽略。

void Add(int x) { F[x] = x; }

练习题目

1. A Bug’s Life

题目描述

两两虫子相互多次交配,找出同性恋虫子。

AC代码

/**
 * https://vjudge.csgrandeur.cn/problem/HDU-1829
 * \category: 并查集
 * 
 * 
 **/
#include <algorithm>
#include <cmath>
#include <cstring>
#include <iostream>
#include <numeric>
#include <set>

using namespace std;

int n;
int t = 0;
const int kN = 100100;
int f[2 * kN];

int Find(int x) { return f[x] == x ? x : f[x] = Find(f[x]); }

inline void solve() {
  int flag = 0;
  int q; cin >> n >> q;
  iota(f, f + (n * 2) + 1, 0);
  for (int i = 0; i < q; ++i) {
    int x, y; cin >> x >> y;
    if (flag) continue;
    /** Find x & y. */
    x = Find(x), y = Find(y);
    /** Union. */
    int a = Find(x + n), b = Find(y + n);
    if (x != y) f[a] = y, f[b] = x;
    else flag = 1;
  }
  cout << "Scenario #" << ++t << ":\n"; 
  if (!flag) cout << "No suspicious bugs found!" << '\n';
  else cout << "Suspicious bugs found!" << '\n';
}

bool rt = true;
signed main () {
  ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
  #ifndef ONLINE_JUDGE
  freopen("test.in", "r", stdin);
  #endif
  if (rt) { int T; cin >> T; while(T--) solve(); }
  else solve();
  return (0 ^ 0);
}

2. 超市

并查集优化

AC代码

/**
  * \link: https://www.acwing.com/problem/content/147/
  * \category: 二叉堆 并查集 贪心
  * 
  *
  * \note: 
  **/
#include <algorithm>
#include <array>
#include <iostream>
#include <string>
#include <utility>
#include <vector>
#include <numeric>

// #define ONLINE_JUDGE
using namespace std;
using PI = pair<int, int>;

const int kN = 1e4 + 233;
array<int, kN> f;
vector<PI> a;
/** 并查集 */
int Find(int x) { return x == f[x] ? x : f[x] = Find(f[x]); } // ! 并查集注意 返回 f[x] = Find(f[x]) !
/** 贪心 + 并查集优化 */
inline void solve() {
  a.reserve(kN);
  int n; while (cin >> n) {
    a.clear();
    int time_max = 0;
    int input_value, input_time; 
    for (int i = 1; i <= n; ++i) {
      cin >> input_value >> input_time;
      a.push_back({-input_value, input_time});
      time_max = max(time_max, input_time);
    }
    sort(a.begin(), a.end());
    iota(f.begin(), next(f.begin(), time_max + 1), 0);
    int ans = 0;
    for (auto it : a) {
      input_value = -it.first, input_time = it.second;
      int pos = Find(input_time);
      if (pos) {
        ans += input_value;
        f[pos] = pos - 1;
      }
    }
    cout << ans << '\n';
  }
  return ;
}

bool rt = false;
 int main() {
  ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
  #ifndef ONLINE_JUDGE
  freopen("test.in", "r", stdin);
  #endif
  if (rt) { int T; cin >> T; while(T--) solve(); }
  else solve();
  return (0 ^ 0);
 }

3. 松鼠排序

题目描述

松鼠宝宝有一排n个大小不一的坚果,松鼠宝宝想把坚果从小到大排序,每次他会选择两个坚果a和b每次花费1点力气把这两个坚果交换,爱动脑筋的松鼠宝宝想知道他排完这n个坚果一共需要花费的最少力气是多少?

输入描述:

第一行一个整数n代表坚果数

接下来一行n个整数代表每个坚果的大小(每个坚果大小都不一样,即大小为1-n的一个排列)

1<=n<=1e5

坚果大小x,1<=x<=n

输出描述:

一行输出代表松鼠宝宝花费的最小力气

输入

3
3 2 1

输出

1

AC代码

/**
 * \link:
 * \category:
 *
 * \date:
 * \author: YaeSaraki
 **/

#include <algorithm>
#include <iostream>
#include <vector>
#include <numeric>

using namespace std;

vector<int> f;

/** using 并查集. */
inline void solve2() {
  int n; cin >> n;
  /** init. */
  vector<int> a(n + 1);
  f = a;
  iota(f.begin(), f.end(), 0);
  
  for (int i = 1; i <= n; ++i) {
    cin >> a.at(i);
    f.at(a.at(i)) = i;
  }
  int ans = 0;
  for (int i = 1; i <= n; ++i) {
    if (a.at(i) != i) {
      int t = f.at(i);
      swap(a.at(i), a.at(t));
      f.at(a.at(t)) = t;
      ++ans;
    }
  }
  cout << ans << '\n'; 
}


/** compressed version. */
inline void solve() {
  int n; cin >> n;
  vector<int> a(n + 1), b(n + 1);
  for (int i = 1; i <= n; ++i) {
    cin >> a.at(i);
    b.at(a.at(i)) = i;
  }
  int energy = 0;
  for (int i = 1; i <= n; ++i) {
    while (a.at(i) != i) {
      swap(a.at(i), a.at(a.at(i)));
      ++energy;
    }
  }
  cout << energy << '\n';
  return ;
}

bool rt = false;
signed main() {
  ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
#ifndef ONLINE_JUDGE
  freopen("test.in", "r", stdin);
#endif
  if (rt) { int T; cin >> T; while (T--) solve(); }
  else solve2();
  return (0 ^ 0);
}

4. 食物链

题目描述

动物王国中有三类动物 A,B,C,这三类动物的食物链构成了有趣的环形。

A 吃 B,B 吃 C,C 吃 A。

现有 N 个动物,以 1∼N 编号。

每个动物都是 A,B,C 中的一种,但是我们并不知道它到底是哪一种。

有人用两种说法对这 N 个动物所构成的食物链关系进行描述:

第一种说法是 1 X Y,表示 X 和 Y 是同类。

第二种说法是 2 X Y,表示 X 吃 Y。

此人对 N 个动物,用上述两种说法,一句接一句地说出 K 句话,这 K 句话有的是真的,有的是假的。

当一句话满足下列三条之一时,这句话就是假话,否则就是真话。

  1. 当前的话与前面的某些真的话冲突,就是假话;
  2. 当前的话中 X 或 Y 比 N 大,就是假话;
  3. 当前的话表示 X 吃 X,就是假话。

你的任务是根据给定的 N 和 K 句话,输出假话的总数。

输入格式

第一行是两个整数 N 和 K,以一个空格分隔。

以下 K 行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中 D 表示说法的种类。

若 D=1,则表示 X 和 Y 是同类。

若 D=2,则表示 X 吃 Y。

输出格式

只有一个整数,表示假话的数目。

数据范围

1≤N≤50000,
0≤K≤100000

输入样例:

100 7
1 101 1 
2 1 2
2 2 3 
2 3 3 
1 1 3 
2 3 1 
1 5 5

输出样例:

3

AC代码

/**
 * @problem: 240. 食物链
 * @link: https://www.acwing.com/problem/content/242/
 * @category: 并查集
 * @date:
 * @Author: YaeSaraki
 **/
#include <algorithm>
#include <iostream>
#include <vector>
#include <numeric>

#define ALL(v) v.begin(), v.end()
#define DBG(x) std::cout << #x << " = " << (x) << '\n'
//#define int long long

using ll = long long;
using PI = std::pair<int, int>;

int lies;
/** 1 -> n 为主视角, 1 -> n 为同类,n + 1 -> 2n 为猎物, 2n + 1 -> 3n为天敌 */
std::vector<int> f;

int Find(int x) { return x == f[x] ? x : f[x] = Find(f[x]); }

bool Union(int x, int y) {
  if (x == y) return false;
  int a = Find(x), b = Find(y);
  f[a] = b;
  return true;
}

inline void solve() {
  int n, k; std::cin >> n >> k;
  f = std::vector<int> (3 * n + 1);
  std::iota(ALL(f), 0);
  while (k--) {
    int op, x, y; std::cin>> op >> x >> y;
    /* 如果 x 或者 y 大于 动物总数, 则是谎言 */
    if (x > n || y > n) { ++lies;
      continue; }

    switch(op) {
      case 1 : {
        /* 如果 x 的 猎物 是 y 或 x 的 天敌 是 y, 则是谎言 */
        if (Find(x + n) == Find(y) || Find(x + 2 * n) == Find(y)) { ++lies; break; }

        /* x 与 y 是 同类 */
        Union(x, y);

        /* x 的 猎物 是 y 的 猎物 */
        Union(x + n, y + n);

        /* x 的 天敌 是 y 的 天敌 */
        Union(x + 2 * n, y + 2 * n);

        break;
      }
      case 2 : {
        /* 如果 x 与 y 是 同类 或 x 的 天敌 是 y, 则是谎言 */
        if (Find(x) == Find(y) || Find(x + 2 * n) == Find(y)) { ++lies; break; }

        /* x 的 猎物 是 y */
        Union(x + n, y);

        /* y 的 天敌 是 x */
        Union(y + 2 * n, x);

        /* y 的 猎物 是 x 的 天敌 */
        Union(y + n, x + 2 * n);

        break;
      }
      default: break;
    }
  }
  std::cout << lies << '\n';
}

bool rt = false;
signed main() {
  std::ios::sync_with_stdio(false); std::cin.tie(nullptr); std::cout.tie(nullptr);
#ifndef ONLINE_JUDGE
  freopen("test.in", "r", stdin);
#endif
  if (rt) { int T; std::cin >> T; while (T--) solve(); }
  else solve();
  return (0 ^ 0);
}


  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值