题目描述
给定 NNN 个节点,每个节点标有一个介于 111 到 10610^6106 之间的整数(包含且标签不一定唯一)。当且仅当两个节点标签的 GCD\texttt{GCD}GCD(最大公约数)大于 111 时,这两个节点之间存在一条边。计算图中连通分量的数量。
输入格式
第一行输入 TTT(T≤100T \leq 100T≤100)表示测试用例的数量。随后是 TTT 个测试用例。每个测试用例包含两行:第一行是一个数字 NNN(1≤N≤1051 \leq N \leq 10^51≤N≤105)表示节点数量;第二行包含 NNN 个数字,其中第 iii 个(1≤i≤N1 \leq i \leq N1≤i≤N)数字 XiX_iXi(1≤Xi≤1061 \leq X_i \leq 10^61≤Xi≤106)表示节点 iii 的标签。
输出格式
对于每个测试用例,输出一行,包含用例编号和一个整数,表示连通分量的数量。
样例输入
2
3
2 3 4
2
1 1
样例输出
Case 1: 2
Case 2: 2
题目分析
这是一个图论问题,需要计算无向图中的连通分量数量。图的构建规则是:如果两个节点标签的 GCD>1\text{GCD} > 1GCD>1,则它们之间有一条边。
关键观察
-
直接建图不可行:节点数量最多可达 10510^5105,如果直接两两比较计算 GCD\text{GCD}GCD 并建图,时间复杂度为 O(N2)O(N^2)O(N2),会超时。
-
质因数分解是关键:如果两个数有公共质因子,那么它们的 GCD>1\text{GCD} > 1GCD>1。因此,我们可以通过质因数分解来间接判断连通性。
-
特殊处理标签 111:标签为 111 的节点与其他任何节点的 GCD\text{GCD}GCD 都是 111,因此每个标签为 111 的节点都是一个孤立的连通分量。
解题思路
核心思想:并查集 + 质因数分解
-
预处理质数表:使用埃拉托斯特尼筛法预处理出 111 到 10610^6106 范围内的所有质数。
-
使用并查集维护连通性:
- 初始化并查集,每个数都是独立的集合
- 对于每个节点标签 xxx(x≠1x \neq 1x=1),分解其质因数
- 将节点 xxx 与其所有质因子合并到同一个集合中
- 这样,如果两个节点共享至少一个质因子,它们就会在同一个集合中
-
统计连通分量:
- 统计标签为 111 的节点数量
- 统计非 111 节点在并查集中的不同根的数量
- 总连通分量数 = 非 111 节点的连通分量数 + 111 的节点数量
算法正确性证明
假设节点 AAA 和节点 BBB 的标签分别为 aaa 和 bbb,且 GCD(a,b)>1\text{GCD}(a, b) > 1GCD(a,b)>1,那么 aaa 和 bbb 至少有一个公共质因子 ppp。在算法中:
- AAA 会与质因子 ppp 合并
- BBB 也会与质因子 ppp 合并
- 因此 AAA 和 BBB 通过 ppp 连接在同一个集合中
反之,如果 AAA 和 BBB 在同一个集合中,说明它们通过一系列质因子连接,这意味着它们有公共质因子,因此 GCD(a,b)>1\text{GCD}(a, b) > 1GCD(a,b)>1。
时间复杂度分析
- 筛法预处理:O(MloglogM)O(M \log \log M)O(MloglogM),其中 M=106M = 10^6M=106
- 质因数分解:每个数最多有 O(logM)O(\log M)O(logM) 个质因子
- 并查集操作:近似 O(α(N))O(\alpha(N))O(α(N)),其中 α\alphaα 是反阿克曼函数
- 总体复杂度:O(NlogM)O(N \log M)O(NlogM),能够处理最大数据范围
代码实现
// Number of Connected Components
// UVa ID: 13153
// Verdict: Accepted
// Submission Date: 2025-11-25
// UVa Run Time: 0.170s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
const int MAX_A = 1000000;
vector<int> primes;
bool isPrime[MAX_A + 1];
int parent[MAX_A + 1];
int findRoot(int x) {
return parent[x] == x ? x : parent[x] = findRoot(parent[x]);
}
void unionSets(int a, int b) {
int ra = findRoot(a), rb = findRoot(b);
if (ra != rb) parent[ra] = rb;
}
void sieve() {
memset(isPrime, true, sizeof(isPrime));
for (int i = 2; i <= MAX_A; i++) {
if (isPrime[i]) {
primes.push_back(i);
for (int j = i + i; j <= MAX_A; j += i)
isPrime[j] = false;
}
}
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
sieve();
int T;
cin >> T;
for (int caseNum = 1; caseNum <= T; caseNum++) {
int n;
cin >> n;
vector<int> labels(n);
int onesCount = 0;
for (int i = 0; i < n; i++) {
cin >> labels[i];
if (labels[i] == 1) onesCount++;
}
for (int i = 1; i <= MAX_A; i++)
parent[i] = i;
for (int x : labels) {
if (x == 1) continue;
int temp = x;
for (int p : primes) {
if (p * p > temp) break;
if (temp % p == 0) {
unionSets(x, p);
while (temp % p == 0) temp /= p;
}
}
if (temp > 1) unionSets(x, temp);
}
unordered_set<int> roots;
for (int x : labels) {
if (x != 1) roots.insert(findRoot(x));
}
int components = roots.size() + onesCount;
cout << "Case " << caseNum << ": " << components << "\n";
}
return 0;
}
总结
本题的关键在于将 GCD\text{GCD}GCD 判断转化为质因子共享问题,通过并查集高效维护连通性。这种"间接建图"的思想在很多图论问题中都有应用,特别是当直接建图不可行时。算法的时间复杂度为 O(NlogM)O(N \log M)O(NlogM),空间复杂度为 O(M)O(M)O(M),能够高效处理题目给出的数据范围。

1324

被折叠的 条评论
为什么被折叠?



