当前位置:   article > 正文

美团校招机试 - 小美的朋友关系(20240309-T4)_小美认为,在人际交往中,但是随着时间的流逝,朋友的关系也是会慢慢变淡的,最终朋友

小美认为,在人际交往中,但是随着时间的流逝,朋友的关系也是会慢慢变淡的,最终朋友

题目来源

美团校招笔试真题_小美的朋友关系

题目描述

小美认为,在人际交往中,但是随着时间的流逝,朋友的关系也是会慢慢变淡的,最终朋友关系就淡忘了。

现在初始有一些朋友关系,存在一些事件会导致两个人淡忘了他们的朋友关系。

小美想知道某一时刻中,某两人是否可以通过朋友介绍互相认识?

事件共有 2 种:

  • 1 u v:代表编号 u 的人和编号 v 的人淡忘了他们的朋友关系。
  • 2 u v:代表小美查询编号 u 的人和编号 v 的人是否能通过朋友介绍互相认识。

注:介绍可以有多层,比如 2 号把 1 号介绍给 3 号,然后 3 号再把 1 号介绍给 4 号,这样 1 号和 4 号就认识了。

输入描述

第一行输入三个正整数 n,m,q,代表总人数,初始的朋友关系数量,发生的事件数量。

接下来的 m 行,每行输入两个正整数 u,v,代表初始编号 u 的人和编号 v 的人是朋友关系。

接下来的 q 行,每行输入三个正整数 op,u,v,含义如题目描述所述。

  • 1 ≤ n ≤ 10^9
  • 1 ≤ m, q ≤ 10^5
  • 1 ≤ u, v ≤ n
  • 1 ≤ op ≤ 2

保证至少存在一次查询操作

输出描述

对于每次 2 号操作,输出一行字符串代表查询的答案。

如果编号 u 的人和编号 v 的人能通过朋友介绍互相认识,则输出"Yes"。否则输出"No"。

用例

输入5 3 5
1 2
2 3
4 5
1 1 5
2 1 3
2 1 4
1 1 2
2 1 3
输出Yes
No
No
说明第一次事件,1 号和 5 号本来就不是朋友,所以无事发生。
第二次事件是询问,1 号和 3 号可以通过 2 号的介绍认识。
第三次事件是询问,显然 1 号和 4 号无法互相认识。
第四次事件,1 号和 2 号淡忘了。
第五次事件,此时 1 号无法再经过 2 号和 3 号互相认识了。

题目解析

本题可以使用并查集来构建朋友关系网。

如果你还不了解并查集,可以先看下这个视频讲解:

《算法训练营》进阶篇 01 并查集_哔哩哔哩_bilibili

当我们了解并查集后,并查集常规操作只有两个:

  • find(i):查找 i 点的根(祖先)
  • union(x,y):合并 x 点所在集合和 y 点所在集合

但是本题的输入存在特点如下:

接下来的 m 行,每行输入两个正整数 u,v,代表初始编号 u 的人和编号 v 的人是朋友关系。

接下来的 q 行,每行输入三个正整数 op,u,v,含义如题目描述所述

先会输入m行来构建朋友关系,之后输入的q行中存在op==1的操作,来断开某个两个点的朋友关系。

比如:

5 3 5
1 2     // 构建 1, 2 的朋友关系
2 3     // 构建 2, 3 的朋友关系,此时 (1,2,3) 在一个朋友圈中
4 5
1 1 5
2 1 3   // 查询1,3是否是朋友,由于此时 (1,2,3) 在一个朋友圈,因此1,3是朋友
2 1 4
1 1 2  // 断开1,2的朋友关系,即1,2不是朋友了,此时 (1) 和 (2,3) 不在一个朋友圈了
2 1 3  // 查询1,3是否是朋友,之前1是通过2认识3的,现在1和2不是朋友了,因此1和3也不是朋友

常规并查集是没有删除关系的操作的,因此对于上面这种先构建,后删除关系的操作,并查集是无法实现。

那么此题该怎么做呢?

我们可以先不关注op==2的查询动作,只关注m行输入的构建关系动作,以及q行输入中的删除关系动作。提取如下:

// 构建关系

1 2

2 3

4 5

// 删除关系

1 1 5

1 1 2

其中:

  • 1,2先构建后删除,因此最终关系中1,2不是朋友关系
  • 1,5本来就没有构建朋友关系,因此删除关系动作是冗余的

因此最终朋友圈关系如下:

// 构建关系

1 2

2 3

4 5

// 删除关系

1 1 5

1 1 2

即最终朋友圈关系中,只有2,3是朋友关系,4,5是朋友关系。

我们让并查集直接构造最终的朋友圈关系即可。

得到最终朋友圈关系后,我们可以倒序执行q行操作:

1 1 5
2 1 3
2 1 4
1 1 2
2 1 3

首先,执行最后一个操作,2 1 3,查询1,3是否是朋友关系,我们根据并查集的最终构建结果,可以得出1,3不是朋友关系


1 1 5
2 1 3
2 1 4
1 1 2
2 1 3

之后,执行倒数第二个操作,正序时,1 1 2表示删除1,2的朋友关系,然后得到最终朋友圈关系。此时逆序,则应该基于最终朋友圈关系,恢复构建1,2的朋友关系,即此时朋友圈关系变为:

  • 1,2,3是朋友关系
  • 4,5是朋友关系

1 1 5
2 1 3
2 1 4
1 1 2
2 1 3

之后,执行倒数第三个操作,2 1 4,查询1,4是否是朋友关系,基于朋友圈关系,可知1,4不是朋友关系


1 1 5
2 1 3
2 1 4
1 1 2
2 1 3

之后,执行倒数第四个操作,2 1 3,查询1,3是否是朋友关系,基于朋友圈关系 ,可知1,3是朋友关系


1 1 5
2 1 3
2 1 4
1 1 2
2 1 3

最后,执行倒数第五个操作,1 1 5,此时我们需要注意的是,正序时,1 1 5 是删除1,5的朋友关系,但是正序时此步之前1,5并非朋友关系,因此此步删除关系操作是失败的,无效的。因此倒序时,我们不应该恢复 1 5 的朋友关系。

因此,我们可以在正序遍历q行操作时,把op==1,但是删除关系失败的操作剔除掉,以免倒序时错误的构建不存在的朋友关系。

总结一下就是:

为了避免在并查集中做删除关系的动作,我们可以:

  1. 记录待构建的朋友关系到一个列表中
  2. 将待构建的朋友关系列表中,之后会被删除的朋友关系剔除掉
  3. 得到最终朋友圈关系的构建列表,此时该列表只有构建动作,没有删除动作
  4. 通过并查集来构建出最终状态的朋友圈关系

之后,我们再逆向q行操作:

由于正向q行操作中不断地删除朋友关系,得到最终状态朋友圈关系。

因此逆向q行操作时,我们基于最终状态朋友圈关系,应该将原本的删除操作,变为构建操作,这样才能恢复为对应步骤正向时朋友圈关系。

JS算法源码

时而超时,时而不超时,多运行几遍,总会有AC的时候。

  1. const rl = require("readline").createInterface({ input: process.stdin });
  2. var iter = rl[Symbol.asyncIterator]();
  3. const readline = async () => (await iter.next()).value;
  4. // 并查集实现
  5. class UnionFindSet {
  6. constructor() {
  7. this.fa = new Map();
  8. }
  9. find(i) {
  10. // 若fa[i]不存在,则初始化fa[i]
  11. if (!this.fa.has(i)) {
  12. this.fa.set(i, i);
  13. }
  14. // 找根的同时,进行路径压缩
  15. if (i != this.fa.get(i)) {
  16. this.fa.set(i, this.find(this.fa.get(i)));
  17. }
  18. // 返回 i 的根
  19. return this.fa.get(i);
  20. }
  21. union(x, y) {
  22. const x_fa = this.find(x);
  23. const y_fa = this.find(y);
  24. // 若x,y的根不同,则合并
  25. if (x_fa != y_fa) {
  26. this.fa.set(y_fa, x_fa);
  27. }
  28. }
  29. }
  30. void (async function () {
  31. const [n, m, q] = (await readline()).split(" ").map(Number);
  32. // m行:建立朋友关系
  33. // 记录待关联的朋友关系 {u, v}
  34. const waitUnion = new Set();
  35. for (let i = 0; i < m; i++) {
  36. const [u, v] = (await readline()).split(" ").map(Number);
  37. const key = u > v ? `${u} ${v}` : `${v} ${u}`;
  38. waitUnion.add(key);
  39. }
  40. // q行(查询或解除朋友关系)操作
  41. const opList = [];
  42. for (let i = 0; i < q; i++) {
  43. const [op, u, v] = (await readline()).split(" ").map(Number);
  44. // op为1,表示解除u,v的关联关系
  45. const key = u > v ? `${u} ${v}` : `${v} ${u}`;
  46. if (op == 1 && !waitUnion.delete(key)) {
  47. // 如果存在{u, v}朋友关系,则解除成功
  48. // 如果不存在{u, v}朋友关系,则此行解除操作无效,可以丢弃,后续反向并查集时,不需要冗余添加
  49. continue;
  50. }
  51. // 记录有效操作:op==2的查询操作,op==1且有效解除操作
  52. opList.push([op, u, v]);
  53. }
  54. // waitUnion记录的是最终的朋友关系(即,经历了m行建立朋友关系,和q行解除朋友关系后的最终朋友关联关系)
  55. const ufs = new UnionFindSet();
  56. for (let s of waitUnion) {
  57. const [u, v] = s.split(" ").map(Number);
  58. ufs.union(u, v);
  59. }
  60. // 反向操作
  61. const res = [];
  62. for (let i = opList.length - 1; i >= 0; i--) {
  63. const [op, u, v] = opList[i];
  64. if (op == 1) {
  65. // 由于此时ufs记录的已经是最终朋友关联关系,因此反向操作时,遇到op==1,原本正向时是解除朋友关系,则反向时是建立朋友关系
  66. ufs.union(u, v);
  67. } else {
  68. // op == 2 的查询操作,查询u, v的朋友关系, 注意此时res是反向记录的
  69. res.push(ufs.find(u) == ufs.find(v));
  70. }
  71. }
  72. // 最终需要正向打印查询结果,因此res需要倒叙
  73. for (let i = res.length - 1; i >= 0; i--) {
  74. console.log(res[i] ? "Yes" : "No");
  75. }
  76. })();

Java算法源码

Java需要使用高效读取类 StreamTokenizer,以及高效打印类 PrintWriter,即可AC,否则会超时

  1. import java.io.*;
  2. import java.util.*;
  3. public class Main {
  4. // 高效读取
  5. static StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
  6. static int nextInt() throws IOException {
  7. st.nextToken();
  8. return (int) st.nval;
  9. }
  10. // 高效打印
  11. static PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
  12. // 并查集的找根数组fa
  13. static HashMap<Integer, Integer> fa = new HashMap<>();
  14. // 并查集的找根方法
  15. static int find(int i) {
  16. // 如果fa不存在i,则初始化fa[i]
  17. if (!fa.containsKey(i)) {
  18. fa.put(i, i);
  19. }
  20. // 找根的同时,进行路径压缩
  21. if (i != fa.get(i)) {
  22. fa.put(i, find(fa.get(i)));
  23. }
  24. return fa.get(i);
  25. }
  26. // 并查集的合并方法
  27. static void union(int x, int y) {
  28. int x_fa = find(x);
  29. int y_fa = find(y);
  30. // x,y的根不同,则可以合并x分量和y分量
  31. if (x_fa != y_fa) {
  32. fa.put(y_fa, x_fa);
  33. }
  34. }
  35. public static void main(String[] args) throws IOException {
  36. int n = nextInt();
  37. int m = nextInt();
  38. int q = nextInt();
  39. // m行:建立朋友关系
  40. // 记录待关联的朋友关系 {u, v}
  41. HashSet<String> waitUnion = new HashSet<>();
  42. for (int i = 0; i < m; i++) {
  43. int u = nextInt();
  44. int v = nextInt();
  45. String key = u > v ? u + " " + v : v + " " + u;
  46. waitUnion.add(key);
  47. }
  48. // q行(查询或解除朋友关系)操作
  49. ArrayList<int[]> opList = new ArrayList<>();
  50. for (int i = 0; i < q; i++) {
  51. int op = nextInt();
  52. int u = nextInt();
  53. int v = nextInt();
  54. // op为1,表示解除u,v的关联关系
  55. String key = u > v ? u + " " + v : v + " " + u;
  56. if (op == 1 && !waitUnion.remove(key)) {
  57. // 如果存在{u, v}朋友关系,则解除成功
  58. // 如果不存在{u, v}朋友关系,则此行解除操作无效,可以丢弃,后续反向并查集时,不需要冗余添加
  59. continue;
  60. }
  61. // 记录有效操作:op==2的查询操作,op==1且有效解除操作
  62. opList.add(new int[]{op, u, v});
  63. }
  64. // waitUnion记录的是最终的朋友关系(即,经历了m行建立朋友关系,和q行解除朋友关系后的最终朋友关联关系)
  65. for (String s : waitUnion) {
  66. String[] tmp = s.split(" ");
  67. int u = Integer.parseInt(tmp[0]);
  68. int v = Integer.parseInt(tmp[1]);
  69. union(u, v);
  70. }
  71. // 反向操作
  72. ArrayList<Boolean> res = new ArrayList<>();
  73. for (int i = opList.size() - 1; i >= 0; i--) {
  74. int op = opList.get(i)[0];
  75. int u = opList.get(i)[1];
  76. int v = opList.get(i)[2];
  77. if (op == 1) {
  78. // 由于此时ufs记录的已经是最终朋友关联关系,因此反向操作时,遇到op==1,原本正向时是解除朋友关系,则反向时是建立朋友关系
  79. union(u, v);
  80. } else {
  81. // op == 2 的查询操作,查询u, v的朋友关系, 注意此时res是反向记录的
  82. res.add(find(u) == find(v));
  83. }
  84. }
  85. // 最终需要正向打印查询结果,因此res需要倒叙
  86. for (int i = res.size() - 1; i >= 0; i--) {
  87. pw.println(res.get(i) ? "Yes" : "No");
  88. }
  89. pw.flush();
  90. }
  91. }

常规Scanner读取和System.out打印方式会超时,并且"并查集"单独定义类也会导致超时,真是佛了。其余逻辑是一样的,但是最终耗时天差地别。

  1. import java.util.*;
  2. public class Main {
  3. public static void main(String[] args) {
  4. Scanner sc = new Scanner(System.in);
  5. int n = sc.nextInt();
  6. int m = sc.nextInt();
  7. int q = sc.nextInt();
  8. // m行:建立朋友关系
  9. // 记录待关联的朋友关系 {u, v}
  10. HashSet<String> waitUnion = new HashSet<>();
  11. for (int i = 0; i < m; i++) {
  12. int u = sc.nextInt();
  13. int v = sc.nextInt();
  14. String key = u > v ? u + " " + v : v + " " + u;
  15. waitUnion.add(key);
  16. }
  17. // q行(查询或解除朋友关系)操作
  18. ArrayList<int[]> opList = new ArrayList<>();
  19. for (int i = 0; i < q; i++) {
  20. int op = sc.nextInt();
  21. int u = sc.nextInt();
  22. int v = sc.nextInt();
  23. // op为1,表示解除u,v的关联关系
  24. String key = u > v ? u + " " + v : v + " " + u;
  25. if (op == 1 && !waitUnion.remove(key)) {
  26. // 如果存在{u, v}朋友关系,则解除成功
  27. // 如果不存在{u, v}朋友关系,则此行解除操作无效,可以丢弃,后续反向并查集时,不需要冗余添加
  28. continue;
  29. }
  30. // 记录有效操作:op==2的查询操作,op==1且有效解除操作
  31. opList.add(new int[]{op, u, v});
  32. }
  33. // waitUnion记录的是最终的朋友关系(即,经历了m行建立朋友关系,和q行解除朋友关系后的最终朋友关联关系)
  34. UnionFindSet ufs = new UnionFindSet();
  35. for (String s : waitUnion) {
  36. String[] tmp = s.split(" ");
  37. int u = Integer.parseInt(tmp[0]);
  38. int v = Integer.parseInt(tmp[1]);
  39. ufs.union(u, v);
  40. }
  41. // 反向操作
  42. ArrayList<Boolean> res = new ArrayList<>();
  43. for (int i = opList.size() - 1; i >= 0; i--) {
  44. int op = opList.get(i)[0];
  45. int u = opList.get(i)[1];
  46. int v = opList.get(i)[2];
  47. if (op == 1) {
  48. // 由于此时ufs记录的已经是最终朋友关联关系,因此反向操作时,遇到op==1,原本正向时是解除朋友关系,则反向时是建立朋友关系
  49. ufs.union(u, v);
  50. } else {
  51. // op == 2 的查询操作,查询u, v的朋友关系, 注意此时res是反向记录的
  52. res.add(ufs.find(u) == ufs.find(v));
  53. }
  54. }
  55. // 最终需要正向打印查询结果,因此res需要倒叙
  56. for (int i = res.size() - 1; i >= 0; i--) {
  57. System.out.println(res.get(i) ? "Yes" : "No");
  58. }
  59. }
  60. }
  61. // 并查集实现
  62. class UnionFindSet {
  63. HashMap<Integer, Integer> fa;
  64. public UnionFindSet() {
  65. fa = new HashMap<>();
  66. }
  67. public int find(int i) {
  68. // 若fa[i]不存在,则初始化fa[i]
  69. if (!fa.containsKey(i)) {
  70. fa.put(i, i);
  71. }
  72. // 找根的同时,进行路径压缩
  73. if (i != fa.get(i)) {
  74. fa.put(i, find(fa.get(i)));
  75. }
  76. // 返回 i 的根
  77. return fa.get(i);
  78. }
  79. public void union(int x, int y) {
  80. int x_fa = find(x);
  81. int y_fa = find(y);
  82. // 若x,y的根不同,则合并
  83. if (x_fa != y_fa) {
  84. fa.put(y_fa, x_fa);
  85. }
  86. }
  87. }

Python算法源码

Python这题是真的无力了。。。逻辑是没问题的,只是受限于Python的执行效率

  1. # 并查集实现
  2. class UnionFindSet:
  3. def __init__(self):
  4. self.fa = {}
  5. def find(self, i):
  6. # 若fa[i]不存在,则初始化fa[i]
  7. if i not in self.fa:
  8. self.fa[i] = i
  9. # 找根的同时,进行路径压缩
  10. if i != self.fa[i]:
  11. self.fa[i] = self.find(self.fa[i])
  12. # 返回 i 的根
  13. return self.fa[i]
  14. def union(self, x, y):
  15. x_root = self.find(x)
  16. y_root = self.find(y)
  17. # 若x,y的根不同,则合并
  18. if x_root != y_root:
  19. self.fa[y_root] = x_root
  20. if __name__ == '__main__':
  21. n, m, q = map(int, input().split())
  22. # m行:建立朋友关系
  23. # 记录待关联的朋友关系 {u, v}
  24. waitUnion = set()
  25. for _ in range(m):
  26. u, v = map(int, input().split())
  27. key = (u, v) if u > v else (v, u)
  28. waitUnion.add(key)
  29. # q行(查询或解除朋友关系)操作
  30. opList = []
  31. for _ in range(q):
  32. op, u, v = map(int, input().split())
  33. # op为1,表示解除u,v的关联关系
  34. key = (u, v) if u > v else (v, u)
  35. if op == 1:
  36. if key in waitUnion:
  37. # 如果存在{u, v}朋友关系,则解除成功
  38. waitUnion.remove(key)
  39. else:
  40. # 如果不存在{u, v}朋友关系,则此行解除操作无效,可以丢弃,后续反向并查集时,不需要冗余添加
  41. continue
  42. # 记录有效操作:op==2的查询操作,op==1且有效解除操作
  43. opList.append((op, u, v))
  44. # waitUnion记录的是最终的朋友关系(即,经历了m行建立朋友关系,和q行解除朋友关系后的最终朋友关联关系)
  45. ufs = UnionFindSet()
  46. for u, v in waitUnion:
  47. ufs.union(u, v)
  48. # 反向操作
  49. res = []
  50. for i in range(len(opList) - 1, -1, -1):
  51. op, u, v = opList[i]
  52. if op == 1:
  53. # 由于此时ufs记录的已经是最终朋友关联关系,因此反向操作时,遇到op==1,原本正向时是解除朋友关系,则反向时是建立朋友关系
  54. ufs.union(u, v)
  55. else:
  56. # op == 2 的查询操作,查询u, v的朋友关系, 注意此时res是反向记录的
  57. res.append(ufs.find(u) == ufs.find(v))
  58. # 最终需要正向打印查询结果,因此res需要倒叙
  59. for i in range(len(res) - 1, -1, -1):
  60. print("Yes" if res[i] else "No")

C算法源码

C语言放弃。。。实在不想手搓map和set

  1. #include <stdio.h>
  2. #include <stdbool.h>
  3. #include <stdlib.h>
  4. /** 并查集定义 **/
  5. typedef struct {
  6. int *fa;
  7. } UFS;
  8. UFS *new_UFS(int n) {
  9. UFS *ufs = (UFS *) malloc(sizeof(UFS));
  10. ufs->fa = (int *) malloc(sizeof(int) * n);
  11. for (int i = 0; i < n; i++) {
  12. ufs->fa[i] = i;
  13. }
  14. return ufs;
  15. }
  16. int find_UFS(UFS *ufs, int x) {
  17. if (x != ufs->fa[x]) {
  18. ufs->fa[x] = find_UFS(ufs, ufs->fa[x]);
  19. }
  20. return ufs->fa[x];
  21. }
  22. void union_UFS(UFS *ufs, int x, int y) {
  23. int x_fa = find_UFS(ufs, x);
  24. int y_fa = find_UFS(ufs, y);
  25. if (x_fa != y_fa) {
  26. ufs->fa[y_fa] = x_fa;
  27. }
  28. }
  29. #define MAX_M 100000
  30. #define MAX_Q 100000
  31. int waitUnion[MAX_M][2];
  32. int opList[MAX_Q][3];
  33. bool opList_effect[MAX_Q] = {false};
  34. bool res[MAX_Q];
  35. int main() {
  36. int n, m, q;
  37. scanf("%d %d %d", &n, &m, &q);
  38. for (int i = 0; i < m; i++) {
  39. int u, v;
  40. scanf("%d %d", &u, &v);
  41. waitUnion[i][0] = u;
  42. waitUnion[i][1] = v;
  43. }
  44. for (int i = 0; i < q; i++) {
  45. int op, u, v;
  46. scanf("%d %d %d", &op, &u, &v);
  47. opList[i][0] = op;
  48. opList[i][1] = u;
  49. opList[i][2] = v;
  50. if (op == 1) {
  51. int j = 0;
  52. for (; j < m; j++) {
  53. if ((waitUnion[j][0] == u && waitUnion[j][1] == v) || (waitUnion[j][0] == v && waitUnion[j][1] == u)) {
  54. waitUnion[j][0] = 0;
  55. waitUnion[j][1] = 0;
  56. break;
  57. }
  58. }
  59. if (j == m) {
  60. continue;
  61. }
  62. }
  63. opList_effect[i] = true;
  64. }
  65. UFS *ufs = new_UFS(n + 1);
  66. for (int i = 0; i < m; i++) {
  67. int u = waitUnion[i][0];
  68. int v = waitUnion[i][1];
  69. if (u == 0 && v == 0) continue;
  70. union_UFS(ufs, u, v);
  71. }
  72. for (int i = q - 1; i >= 0; i--) {
  73. if(!opList_effect[i]) continue;
  74. int op = opList[i][0];
  75. int u = opList[i][1];
  76. int v = opList[i][2];
  77. if (op == 1) {
  78. union_UFS(ufs, u, v);
  79. } else {
  80. res[i] = find_UFS(ufs, u) == find_UFS(ufs, v);
  81. }
  82. }
  83. for (int i = 0; i < q; i++) {
  84. if (opList[i][0] == 2) {
  85. printf("%s\n", res[i] ? "Yes" : "No");
  86. }
  87. }
  88. return 0;
  89. }

C++算法源码

可以AC

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. class UnionFindSet {
  4. public:
  5. map<int, int> fa;
  6. int find(int i) {
  7. // 若fa[i]不存在,则初始化fa[i]
  8. if (!fa[i]) {
  9. return fa[i] = i;
  10. }
  11. // 找根的同时,进行路径压缩
  12. if (i != fa[i]) {
  13. fa[i] = find(fa[i]);
  14. }
  15. // 返回 i 的根
  16. return fa[i];
  17. }
  18. void merge(int x, int y) {
  19. int x_fa = find(x);
  20. int y_fa = find(y);
  21. // 若x,y的根不同,则合并
  22. if (x_fa != y_fa) {
  23. fa[y_fa] = x_fa;
  24. }
  25. }
  26. };
  27. int main() {
  28. int n, m, q;
  29. cin >> n >> m >> q;
  30. // m行:建立朋友关系
  31. // 记录待关联的朋友关系 {u, v}
  32. set<pair<int, int>> waitUnion;
  33. for (int i = 0; i < m; i++) {
  34. int u, v;
  35. cin >> u >> v;
  36. pair<int, int> key = u > v ? pair<int, int>{u, v} : pair<int, int>{v, u};
  37. waitUnion.insert(key);
  38. }
  39. // q行(查询或解除朋友关系)操作
  40. vector<int *> opList;
  41. for (int i = 0; i < q; i++) {
  42. int op, u, v;
  43. cin >> op >> u >> v;
  44. // op为1,表示解除u,v的关联关系
  45. if (op == 1) {
  46. pair<int, int> key = u > v ? pair<int, int>{u, v} : pair<int, int>{v, u};
  47. // 如果存在{u, v}朋友关系,则解除成功
  48. // 如果不存在{u, v}朋友关系,则此行解除操作无效,可以丢弃,后续反向并查集时,不需要冗余添加
  49. if (!waitUnion.erase(key)) {
  50. continue; // 如果不丢弃无效解除操作,则最终会超时
  51. }
  52. }
  53. // 记录有效操作:op==2的查询操作,op==1且有效解除操作
  54. opList.emplace_back(new int[]{op, u, v});
  55. }
  56. // waitUnion记录的是最终的朋友关系(即,经历了m行建立朋友关系,和q行解除朋友关系后的最终朋友关联关系)
  57. UnionFindSet ufs;
  58. for (const auto &item: waitUnion) {
  59. ufs.merge(item.first, item.second);
  60. }
  61. // 反向操作
  62. vector<bool> res;
  63. for (int i = opList.size() - 1; i >= 0; i--) {
  64. int op = opList[i][0];
  65. int u = opList[i][1];
  66. int v = opList[i][2];
  67. if (op == 1) {
  68. // 由于此时ufs记录的已经是最终朋友关联关系,因此反向操作时,遇到op==1,原本正向时是解除朋友关系,则反向时是建立朋友关系
  69. ufs.merge(u, v);
  70. } else {
  71. // op == 2 的查询操作,查询u, v的朋友关系, 注意此时res是反向记录的
  72. res.emplace_back(ufs.find(u) == ufs.find(v));
  73. }
  74. }
  75. // 最终需要正向打印查询结果,因此res需要倒叙
  76. for (int i = res.size() - 1; i >= 0; i--) {
  77. cout << (res[i] ? "Yes" : "No") << endl;
  78. }
  79. return 0;
  80. }
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/在线问答5/article/detail/802282
推荐阅读
相关标签
  

闽ICP备14008679号