sdut 4408 这真的是签到题
Problem Description
给你n个整数,a1,a2,a3,......,an。每个整数范围1到1e6。选取任意的i(1<=i<=n)和j(1<=j<=n)如果gcd(ai,aj)>1,ai和aj为一组,如果ai和aj为一组,ai和ak为一组,那么ai,aj,ak为一组,求这n个整数中,最后有多少个组。
Input
输入一个T表示组数(T<=100)。然后输入一个整数n(1<=n<=1e5),最后输入n个整数ai(1<=ai<=1e6)。
Output
每一组测试样例输出一个整数,表示一共有多少组。
Sample Input
2 3 2 3 4 6 2 3 4 5 6 6
Sample Output
2 2
题解思路:
唯一分解定理+并查集
唯一分解定理:每一个数都可以分解为素数的乘积;
筛一下1e6范围内的素数,将数字拆分为素数(要用sqrt()的时间);
并查集维护关系;
选拔赛的题目 还是好菜 自己
#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
const int maxn=1e6+80000;
int prime[maxn],tot=0;
bool is_prime[maxn],vis[maxn];
//数字数否出现
int f[maxn];
int root(int x)
{
if(x==f[x]) return f[x];
else return f[x]=root(f[x]);
}
void make_prime()//欧拉筛
{
is_prime[1]=is_prime[0]=true;
for(int i=2;i<=1e6;i++)
{
if(!is_prime[i])
{
prime[++tot]=i;
}
for(int j=1;j<=tot&&i*prime[j]<=1e6;j++)
{
is_prime[i*prime[j]]=true;
if(i%prime[j]==0)
{
break;
}
}
}
}
int main(){
make_prime();
// for(int i=1;i<=30;i++)
// {
// cout<<prime[i]<<endl;
// }
int t;
scanf("%d",&t);
while(t--)
{
int n,now;
mem(vis,false);
scanf("%d",&n);
for(int i=1;i<=n+tot;i++)
{
f[i]=i;
}
for(int i=1;i<=n;i++)
{
scanf("%d",&now);
vis[i]=true;
for(int j=1;j<=tot&&(prime[j]*prime[j]<=now);j++)//根下拆分
{
if(!(now%prime[j]))
{
vis[n+j]=true;
int x=root(i),y=root(n+j);
f[x]=y;
while(!(now%prime[j]))
now/=prime[j];
}
}
if(now!=1)//如果拆到最后不是1 说明now为一个比根下now大的素数
{
int x=root(i);
int y=n+lower_bound(prime+1,prime+1+tot,now)-prime;
vis[y]=true;
y=root(y);
f[x]=y;
}
}
int ans=0;
// for(int i=1;i<=6;i++)
// {
// cout<<f[i]<<" "<<vis[i]<<endl;
// }
for(int i=1;i<=n+tot;i++)
{
if(f[i]==i&&vis[i])
{
ans++;
}
}
printf("%d\n",ans);
}
return 0;
}