题意:
给定一个数组 (a) ,数组中任意一个元素的因子数不超过 (7) ,找出一个最短的子序列,满足该子序列之积为完全平方数。输出其长度。
数据范围:(1≤n≤10^5,1≤a_i≤10^6)
分析:
首先,对于数组中的每个元素,如果其因子中包含有一个完全平方数,那么可以把该完全平方数除去,不影响最后的结果。
然后,可以发现,当一个数的因子个数 (leq 7) 时,其包含的质因子个数 (leq 2)。(如果有 (3)个质因子,那么至少有 (8) 个因子)当我们把每个数因子中的完全平方数除去后,每个数会变成 (1,p,pq)((p,q) 均为质数)中的一种。接下来,建一个图,图中顶点为质数和 (1),边为数组中元素化简后得到的数字。那么图的意义是什么呢?假设我们从点 (p) 经过一条边走到点 (q),那么我们就可以得到 (p*q)。要想最后得到一个完全平方数,那么必然会有一个点走两次,即整条路径是一个环。要使路径最短,就是要求一个最小环的长度。
我们可以直接对每个点用一遍 (bfs) 来找到最小环,复杂度为:(O(N*M)),显然不行。但其实可以发现,对于两个都大于 (sqrt{a_{max}})的点,它们之间是不可能连边的,各自只能和更小的数的点连边,所以对于这些点,不用以它们为源点进行 (bfs)。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+5;
int a[N],dis[N],ans,num;
bool vis[N];
vector<int>pic[N],prime;
queue<int>que;
void read(int &x)
{
x=0;
int f=1;
char ch=getchar();
while(!isdigit(ch))
{
if(ch=='-')
f=-1;
ch=getchar();
}
while(isdigit(ch))
{
x=(x<<3)+(x<<1)+ch-'0';
ch=getchar();
}
x*=f;
}
int init(int x)
{
for(int i=2;i*i<=x;i++)
{
if(x%i)
continue;
int cnt=0;
while(x%i==0)
{
x/=i;
cnt++;
}
if(cnt&1)
x*=i;
}
return x;
}
void bfs(int s)
{
while(!que.empty())
que.pop();
for(int i=0;i<num;i++)
{
dis[prime[i]]=N;
vis[prime[i]]=0;
}
vis[s]=1;
que.push(s);
dis[s]=0;
while(!que.empty())
{
int now=que.front();
que.pop();
vis[now]=0;
for(int i=0;i<pic[now].size();i++)
{
int t=pic[now][i];
if(dis[t]>dis[now]+1)
{
dis[t]=dis[now]+1;
que.push(t);
vis[t]=1;
}
else if(vis[t])
ans=min(ans,dis[t]+dis[now]+1);
}
}
}
int main()
{
int n,maxn=-1;
read(n);
for(int i=1;i<=n;i++)//预处理,消除完全平方的因子
{
read(a[i]);
maxn=max(a[i],maxn);
a[i]=init(a[i]);
}
sort(a+1,a+1+n);
if(a[1]==1)
{
printf("1
");
return 0;
}
int len=unique(a+1,a+1+n)-a-1;
if(len<n)//有相同元素
{
printf("2
");
return 0;
}
for(int i=1;i<=len;i++)//只有p,pq;1的情况已经被排除
{
vector<int>tmp(2,-1);
tmp[0]=a[i];
for(int j=2;j*j<=a[i];j++)
{
if(a[i]%j==0)
{
a[i]/=j;
tmp[0]=j;
if(a[i]>1)
tmp[1]=a[i];
break;
}
}
if(tmp[1]==-1)
tmp[1]=1;
for(int j=0;j<tmp.size();j++)
prime.push_back(tmp[j]);
pic[tmp[0]].push_back(tmp[1]);
pic[tmp[1]].push_back(tmp[0]);
}
sort(prime.begin(),prime.end());
num=unique(prime.begin(),prime.end())-prime.begin();
ans=N;
for(int i=0;i<num;i++)
{
if(1LL*prime[i]*prime[i]>=maxn)//注意数据范围:如果溢出,每个数都会找一遍,会t
break;
bfs(prime[i]);
}
printf("%d
",ans==N?-1:ans);
return 0;
}