🌟 P2078 朋友 - 情侣配对大作战 题解
题目链接
题目描述
小明和小红想帮公司里的朋友们脱单!公司分为:
- A公司:全是男生(编号为正整数)
- B公司:全是女生(编号为负整数)
朋友关系具有传递性(朋友的朋友也是朋友)。小明(编号1)和小红(编号-1)是朋友。现在需要找出小明和小红朋友圈中能配成的最大情侣对数(情侣必须是一男一女)。
输入格式
- 第一行: N N N(A公司男生数), M M M(B公司女生数), P P P(男生朋友关系数), Q Q Q(女生朋友关系数)
- 接下来 P P P行:每行两个正整数 x , y x, y x,y,表示男生 x x x和 y y y是朋友
- 接下来 Q Q Q行:每行两个负整数 x , y x, y x,y,表示女生 x x x和 y y y是朋友
输出格式
一个整数,表示最大情侣对数
算法思路
核心思想
使用两个并查集分别管理男生和女生的朋友圈:
- 男生圈:管理A公司男性朋友关系
- 女生圈:管理B公司女性朋友关系(负数转正数处理)
最大情侣对数 = min(小明朋友圈男生数, 小红朋友圈女生数)
关键步骤
-
初始化并查集:
- 男生圈: N N N个节点(编号1~ N N N)
- 女生圈: M M M个节点(编号1~ M M M,对应原负数编号绝对值)
-
合并朋友关系:
- 男生关系:直接合并
- 女生关系:取绝对值后合并
-
查询朋友圈大小:
- 小明朋友圈:男生圈中1号节点所在连通块大小
- 小红朋友圈:女生圈中1号节点所在连通块大小
复杂度分析
- 时间复杂度:
O
(
(
P
+
Q
)
α
(
N
+
M
)
)
O((P+Q) \alpha(N+M))
O((P+Q)α(N+M))
- α \alpha α为反阿克曼函数(接近 O ( 1 ) O(1) O(1))
- P P P:男生关系数, Q Q Q:女生关系数
- 空间复杂度: O ( N + M ) O(N+M) O(N+M)
代码实现
#include <iostream>
#include <algorithm>
using namespace std;
const int MAXN = 10010; // 最大员工数
// 并查集结构体
struct UnionFind {
int parent[MAXN]; // 父节点数组
int size[MAXN]; // 连通块大小
// 初始化并查集
void init(int n) {
for (int i = 1; i <= n; i++) {
parent[i] = i;
size[i] = 1;
}
}
// 查找根节点(带路径压缩)
int find(int x) {
return parent[x] == x ? x : parent[x] = find(parent[x]);
}
// 合并两个节点(按秩合并)
void unite(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX == rootY) return;
// 小树合并到大树
if (size[rootX] < size[rootY])
swap(rootX, rootY);
parent[rootY] = rootX;
size[rootX] += size[rootY];
}
// 获取连通块大小
int getSize(int x) {
return size[find(x)];
}
};
int main() {
int N, M, P, Q;
cin >> N >> M >> P >> Q;
// 初始化并查集
UnionFind boyCircle, girlCircle;
boyCircle.init(N); // 男生圈(A公司)
girlCircle.init(M); // 女生圈(B公司)
// 处理男生朋友关系
while (P--) {
int x, y;
cin >> x >> y;
boyCircle.unite(x, y);
}
// 处理女生朋友关系(负数转正数)
while (Q--) {
int x, y;
cin >> x >> y;
x = abs(x);
y = abs(y);
girlCircle.unite(x, y);
}
// 获取朋友圈大小
int boySize = boyCircle.getSize(1); // 小明朋友圈男生数
int girlSize = girlCircle.getSize(1); // 小红朋友圈女生数
// 最大情侣对数
cout << min(boySize, girlSize) << endl;
return 0;
}
代码解释
数据结构
- UnionFind:并查集结构
parent
:存储每个节点的父节点size
:存储每个连通块的大小
- 核心方法:
init(n)
:初始化 n n n个独立节点find(x)
:查找根节点(路径压缩优化)unite(x,y)
:合并两个节点(按秩合并优化)getSize(x)
:获取 x x x所在连通块大小
关键逻辑
-
男生圈处理:
boyCircle.unite(x, y); // 直接合并男生关系
-
女生圈处理:
x = abs(x); y = abs(y); // 负数转正数 girlCircle.unite(x, y); // 合并女生关系
-
朋友圈大小计算:
int boySize = boyCircle.getSize(1); // 小明朋友圈大小 int girlSize = girlCircle.getSize(1); // 小红朋友圈大小
-
最大情侣对数:
min(boySize, girlSize); // 一男一女配一对
算法正确性证明
并查集优化
-
路径压缩:
find
操作时扁平化结构,使后续查询 O ( 1 ) O(1) O(1)parent[x] = find(parent[x]); // 路径压缩
-
按秩合并:小树合并到大树,保证树高 O ( log n ) O(\log n) O(logn)
if (size[rootX] < size[rootY]) swap(rootX, rootY); size[rootX] += size[rootY];
情侣对数计算
设:
- B m a x B_{max} Bmax = 小明朋友圈男生数
- G m a x G_{max} Gmax = 小红朋友圈女生数
则最大情侣对数 P m a x = min ( B m a x , G m a x ) P_{max} = \min(B_{max}, G_{max}) Pmax=min(Bmax,Gmax),因为:
- 每个情侣需要1男1女
- 男生必须来自小明朋友圈
- 女生必须来自小红朋友圈
- 情侣配对互不影响
测试样例
输入 #1
4 3 4 2
1 1
1 2
2 3
1 3
-1 -2
-3 -3
输出 #1
2
解释:
- 男生圈:{1,2,3}连通(大小=3)
- 女生圈:{1,2}连通(大小=2)
- 最大情侣对数 = min(3,2) = 2
输入 #2
5 4 3 2
1 2
2 3
4 5
-1 -2
-3 -4
输出 #2
2
解释:
- 男生圈:{1,2,3}连通(大小=3),{4,5}连通(大小=2)
- 女生圈:{1,2}连通(大小=2),{3,4}连通(大小=2)
- 小明朋友圈(节点1):大小=3
- 小红朋友圈(节点1):大小=2
- 最大情侣对数 = min(3,2) = 2
拓展思考
- 如果小明和小红可以配对:代码已包含(小明在男生圈,小红在女生圈)
- 多公司扩展:可通过增加并查集数量支持
- 动态关系处理:使用动态并查集(带删除操作)
复杂度证明
时间复杂度
设 n = N + M n = N + M n=N+M, m = P + Q m = P + Q m=P+Q:
- 并查集操作: O ( α ( n ) ) O(\alpha(n)) O(α(n))每次
- 总时间: O ( m α ( n ) ) ≈ O ( m ) O(m \alpha(n)) \approx O(m) O(mα(n))≈O(m)(实际效率接近线性)
空间复杂度
- 存储并查集: O ( N + M ) O(N + M) O(N+M)
- 总空间: O ( max ( N , M ) ) O(\max(N,M)) O(max(N,M))