建议访问原文出处,获得更佳浏览体验。
原文出处:https://hyp1231.github.io/2018/07/31/20180731-cf1012b/
题意
给定一个
n×m
n
×
m
的矩阵,其中
q
q
个位置已经被填充。
有一条规则,如果 ,
(r1,c2)
(
r
1
,
c
2
)
,
(r2,c1)
(
r
2
,
c
1
)
均被填充,则
(r2,c2)
(
r
2
,
c
2
)
也被填充。任何被其他三个位置生成的位置,也可以继续生成其他位置。问最少需要再人为填充多少元素,使矩阵被填满。
链接
题解
我们考虑自动填充的规则,又三个点自动生成第四个点。如果 (r1,c1) ( r 1 , c 1 ) , (r1,c2) ( r 1 , c 2 ) , (r2,c1) ( r 2 , c 1 ) 均被填充,则可以看成:
- (r1,c1) ( r 1 , c 1 ) : r1 r 1 和 c1 c 1 被联系起来
- (r1,c2) ( r 1 , c 2 ) : r1 r 1 和 c2 c 2 被联系起来
- 由 1,2→ 1 , 2 → c1 c 1 和 c2 c 2 被联系起来
- (r2,c1) ( r 2 , c 1 ) : r2 r 2 和 c1 c 1 被联系起来
- 由 3,4→ 3 , 4 → r2 r 2 和 c2 c 2 被联系起来,即有 (r2,c2) ( r 2 , c 2 ) 被填充
这两个模型是等价的。至此,我们把每个边的标号和每个列的标号都看成抽象图中的点,每个给出的点 (r,c) ( r , c ) 看成一个关于点 r r 和点 属于同一集合的声明。故我们可以使用并查集维护,计算出当前矩阵中独立集合的数量 N N 。
考虑需要人工合并的次数(即认为填充元素的数目)。考虑当前矩阵中未被填充的点,人工填充这个点一定可以实现两个集合的合并,总集合数即减 。每次人工填充后均自动填充所有可以被自动填充的点。故 N−1 N − 1 次人工填充后,将全部属于一个集合,矩阵被填满,所以原题的答案即为 N−1 N − 1 。
代码
#include <iostream>
#include <algorithm>
typedef long long LL;
const int N = 200010;
int n, m, q;
int pre[N << 1], rank[N << 1];
bool vis[N << 1];
int find(int x) {
return x == pre[x] ? x : pre[x] = find(pre[x]);
}
void merge(int x, int y) {
int rx = find(x), ry = find(y);
if (rx != ry) {
if (rank[rx] == rank[ry]) {
pre[ry] = rx;
rank[rx]++;
} else if(rank[rx] < rank[ry]) {
pre[rx] = ry;
} else {
pre[ry] = rx;
}
}
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
std::cin >> n >> m >> q;
for (int i = 1; i <= m + n; ++i) {
pre[i] = i;
} // 初始化并查集
int r, c;
for (int i = 0; i < q; ++i) {
std::cin >> r >> c;
c += n; // 列的编号: 1 + n ~ m + n
merge(r, c);
}
long long ans = 0;
for (int i = 1; i <= m + n; ++i) {
int root = find(i);
if (!vis[root]) {
vis[root] = true;
++ans; // 找到新的连通块
}
}
std::cout << ans - 1 << std::endl;
return 0;
}