版权声明:禁止商业用途,如需转载请注明原文出处:https://hyp1231.github.io 或者 https://blog.csdn.net/hyp1231/article/details/81295464
建议访问原文出处,获得更佳浏览体验。
原文出处:https://hyp1231.github.io/2018/07/31/20180731-cf1012b/
题意
给定一个
的矩阵,其中
个位置已经被填充。
有一条规则,如果
,
,
均被填充,则
也被填充。任何被其他三个位置生成的位置,也可以继续生成其他位置。问最少需要再人为填充多少元素,使矩阵被填满。
链接
题解
我们考虑自动填充的规则,又三个点自动生成第四个点。如果 , , 均被填充,则可以看成:
- : 和 被联系起来
- : 和 被联系起来
- 由 和 被联系起来
- : 和 被联系起来
- 由 和 被联系起来,即有 被填充
这两个模型是等价的。至此,我们把每个边的标号和每个列的标号都看成抽象图中的点,每个给出的点 看成一个关于点 和点 属于同一集合的声明。故我们可以使用并查集维护,计算出当前矩阵中独立集合的数量 。
考虑需要人工合并的次数(即认为填充元素的数目)。考虑当前矩阵中未被填充的点,人工填充这个点一定可以实现两个集合的合并,总集合数即减 。每次人工填充后均自动填充所有可以被自动填充的点。故 次人工填充后,将全部属于一个集合,矩阵被填满,所以原题的答案即为 。
代码
#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;
}