UVa 13153 Number of Connected Components

题目描述

给定 NNN 个节点,每个节点标有一个介于 11110610^6106 之间的整数(包含且标签不一定唯一)。当且仅当两个节点标签的 GCD\texttt{GCD}GCD(最大公约数)大于 111 时,这两个节点之间存在一条边。计算图中连通分量的数量。

输入格式

第一行输入 TTTT≤100T \leq 100T100)表示测试用例的数量。随后是 TTT 个测试用例。每个测试用例包含两行:第一行是一个数字 NNN1≤N≤1051 \leq N \leq 10^51N105)表示节点数量;第二行包含 NNN 个数字,其中第 iii 个(1≤i≤N1 \leq i \leq N1iN)数字 XiX_iXi1≤Xi≤1061 \leq X_i \leq 10^61Xi106)表示节点 iii 的标签。

输出格式

对于每个测试用例,输出一行,包含用例编号和一个整数,表示连通分量的数量。

样例输入

2
3
2 3 4
2
1 1

样例输出

Case 1: 2
Case 2: 2

题目分析

这是一个图论问题,需要计算无向图中的连通分量数量。图的构建规则是:如果两个节点标签的 GCD>1\text{GCD} > 1GCD>1,则它们之间有一条边。

关键观察

  1. 直接建图不可行:节点数量最多可达 10510^5105,如果直接两两比较计算 GCD\text{GCD}GCD 并建图,时间复杂度为 O(N2)O(N^2)O(N2),会超时。

  2. 质因数分解是关键:如果两个数有公共质因子,那么它们的 GCD>1\text{GCD} > 1GCD>1。因此,我们可以通过质因数分解来间接判断连通性。

  3. 特殊处理标签 111:标签为 111 的节点与其他任何节点的 GCD\text{GCD}GCD 都是 111,因此每个标签为 111 的节点都是一个孤立的连通分量。

解题思路

核心思想:并查集 + 质因数分解

  1. 预处理质数表:使用埃拉托斯特尼筛法预处理出 11110610^6106 范围内的所有质数。

  2. 使用并查集维护连通性

    • 初始化并查集,每个数都是独立的集合
    • 对于每个节点标签 xxxx≠1x \neq 1x=1),分解其质因数
    • 将节点 xxx 与其所有质因子合并到同一个集合中
    • 这样,如果两个节点共享至少一个质因子,它们就会在同一个集合中
  3. 统计连通分量

    • 统计标签为 111 的节点数量
    • 统计非 111 节点在并查集中的不同根的数量
    • 总连通分量数 = 非 111 节点的连通分量数 + 111 的节点数量

算法正确性证明

假设节点 AAA 和节点 BBB 的标签分别为 aaabbb,且 GCD(a,b)>1\text{GCD}(a, b) > 1GCD(a,b)>1,那么 aaabbb 至少有一个公共质因子 ppp。在算法中:

  • AAA 会与质因子 ppp 合并
  • BBB 也会与质因子 ppp 合并
  • 因此 AAABBB 通过 ppp 连接在同一个集合中

反之,如果 AAABBB 在同一个集合中,说明它们通过一系列质因子连接,这意味着它们有公共质因子,因此 GCD(a,b)>1\text{GCD}(a, b) > 1GCD(a,b)>1

时间复杂度分析

  • 筛法预处理:O(Mlog⁡log⁡M)O(M \log \log M)O(MloglogM),其中 M=106M = 10^6M=106
  • 质因数分解:每个数最多有 O(log⁡M)O(\log M)O(logM) 个质因子
  • 并查集操作:近似 O(α(N))O(\alpha(N))O(α(N)),其中 α\alphaα 是反阿克曼函数
  • 总体复杂度:O(Nlog⁡M)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(Nlog⁡M)O(N \log M)O(NlogM),空间复杂度为 O(M)O(M)O(M),能够高效处理题目给出的数据范围。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值