Number of Connected Components UVALive - 7638(并查集 + 唯一分解)

Given N nodes, each node is labeled with an integer between 1 and 106 (inclusive and labels are not necessarily distinct). Two nodes have an edge between them, if and only if the GCD (Greatest Common Divisor) of the labels of these nodes is greater than 1. Count the number of connected components in the graph.

Input :

First line of the input T (T ≤ 100) denotes the number of testcases. Then T cases follow. Each case consists of 2 lines. The first line has a number N (1 ≤ N ≤ 105 ) denoting the number of nodes. The next line consists of N numbers. The i-th (1 ≤ i ≤ n) number Xi (1 ≤ Xi ≤ 106 ) denotes the label of the node i.

Output:

For each case you have to print a line consisting consisting the case number followed by an integer which denotes the number of connected components. Look at the output for sample input for details.

Sample Input:

2

3

2 3 4

6

2 3 4 5 6 6

Sample Output

Case 1: 2

Case 2: 2

题目大意:

给你n个数,如果两个数的gcd不为1就可以连一条边,问最后共有几个联通块。

思路:

两两求gcd然后建边时间上不允许,既然是gcd不为1,那么也就是至少有一个公共素因子,我们虽然无法直接求gcd,但是可以分解每个数,采用并查集的方法把有公共因子的数连接起来,最后在以O(n)级别的复杂度统计一下联通块数。再唯一分解的时候注意跑到sqrt(n),如果跑到n的话会超时,我先把素数筛了出来,不筛的话应该也行。

#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
#include <map>

using namespace std;

const int maxn = 1e5 + 100;
int prime[1010], pre[maxn], fron[maxn * 10];
bool vis[1010], book[maxn];


void Prime()        //线性O(n)筛素数
{
    memset(vis, false, sizeof(vis));
    prime[0] = 1;
    for(int i = 2; i <= 1010; ++ i)
    {
        if(!vis[i])
            prime[prime[0]++] = i;
        for(int j = 1; j < prime[0] && i * prime[j] <= 1010; ++ j)
        {
            vis[i * prime[j]] = true;
            if(i % prime[j] == 0) break;
        }
    }
}

int finds(int x)    //并查集查找函数
{
    if(pre[x] == x)
        return x;
    return pre[x] = finds(pre[x]);
}

void link(int x, int y)     //连接函数
{
    int a = finds(x), b = finds(y);
    pre[a] = b;
}

int main()
{
    //freopen("in.txt", "r", stdin);
    Prime();            //预处理一波
    int t, n, Case = 0;
    scanf("%d", &t);
    while(t -- )
    {
        scanf("%d", &n);
        for(int i = 0; i <= n; ++ i)
        {
            book[i] = false;
            pre[i] = i;
        }
        memset(fron, -1, sizeof(fron));      //fron[i]代表含有素因子i的集合的标志元素,存的是节点编号,也就是用一个含有该素因子的节点来代替这个集合。
        int x;
        for(int i = 1; i <= n; ++ i)
        {
            scanf("%d", &x);
            int m = x;
            for(int j = 1; j < prime[0] && prime[j] * prime[j] <= m; ++ j)   //跑到sqrt(n)
            {
                if(x % prime[j] == 0)
                {
                    if(fron[prime[j]] == -1)    //如果该素因子没有标志元素,则加上
                    {
                        fron[prime[j]] = i;
                    }
                    else    //有的话连接
                    {
                        link(fron[prime[j]], i);
                    }
                    while(x % prime[j] == 0)
                        x = x / prime[j];
                }
                if(x == 1)
                    break;
            }
            if(x != 1)         //最后在判断一下,剩下的数可能是1,也可能是一个大于sqrt(n)的素数
            {
                    if(fron[x] == -1)
                    {
                        fron[x] = i;
                    }
                    else
                    {
                        link(fron[x], i);
                    }
            }
        }

        for(int i = 1; i <= n; ++ i)
        {
            book[finds(i)] = true;          //把每个集合的标记元素都记录一下
        }
        int sum = 0;
        for(int i = 1; i <= n; ++ i)
        {
            if(book[i])        //统计集合个数
                sum++;
        }
        printf("Case %d: %d\n", ++Case, sum);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/aqa2037299560/article/details/85059246