题意:
给一个长度为n的数组a,数组中的元素满足条件:每个数最多只有7个因子
现在要你选出一段子序列,满足子序列乘积是完全平方数,问子序列的最小长度是多少,输出最小长度,无解则输出-1
数据范围:n<=1e5,a(i)<=1e6
思路:
完全平方数即质因子的幂次都是偶数的数。
一个数最多只有7个因子,可以推出最多只有两种质因子,因为三种质因子的数至少有8个因子。
如果某个数没有奇数次幂的质因子,那么它就是一个完全平方数,输出1即可
如果某个数有一个奇数次幂的质因子,让这个质因子和点1连无向边
如果某个数有两个为奇数次幂的质因子,让这两个质因子间连无向边
则图的最小环就是答案。
最小环的计算常用的是floyd,但是复杂度太高,这题显然不行。
考虑到边权为1,可以枚举所有点作为起点+bfs计算最小环。
但是枚举所有点作为起点也会超时。
考虑最大范围数据,因为数据最大范围为1e6,则大于根号1e6(即大于1e3的点),是不可能和同样大于1e3的点相连的(因为不可能存在一个数有两个大于1e3的质因子),只可能和小于1e3的点相连,因此只需要枚举小于等于1e3的点作为起点就就行了。
ps:
bfs求最小环是第一次见到
画了一下bfs的分层图,发现当x搜到一个已经搜过的v的时候,最小环应该是d(x)+d(v)+1-d(LCA(x,v)),但是大家的代码中都是直接对d(x)+d(v)+1取min,没有管后面的LCA,稍微想了一下发现是因为枚举所有数作为根节点,总会枚举到他们的LCA作为根,这时候就可以上面的式子就可以去掉后面的LCA了,也就是直接取min总会取到正确答案。
对于这题来说,不枚举大于1e3的作也可以,因为枚举到与他相连的点作为LCA也能计算出答案。
code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=1e6+5;
int head[maxm],nt[maxm<<1],to[maxm<<1],w[maxm<<1],cnt,idx;
int d[maxm],mark[maxm];
int ans=1e9;
int n;
void add(int x,int y,int z){
cnt++;nt[cnt]=head[x];head[x]=cnt;to[cnt]=y,w[cnt]=z;
}
bool isprime(int x){
for(int i=2;i*i<=x;i++){
if(x%i==0)return 0;
}
return 1;
}
void bfs(int st){
queue<int>q;
q.push(st);
memset(d,-1,sizeof d);
for(int i=1;i<=idx;i++)mark[i]=0;//清空边标记
d[st]=0;
while(!q.empty()){
int x=q.front();
q.pop();
for(int i=head[x];i;i=nt[i]){
if(mark[w[i]])continue;//回边跳过
mark[w[i]]=1;
int v=to[i];
if(d[v]==-1){
d[v]=d[x]+1;
q.push(v);
}else{
ans=min(ans,d[v]+d[x]+1);
}
}
}
}
signed main(){
scanf("%lld",&n);
for(int i=1;i<=n;i++){
int x;
scanf("%lld",&x);
int a=0,b=1;
for(int j=2;j*j<=x;j++){
if(x%j==0){
int cnt=0;
while(x%j==0){
cnt++;
x/=j;
}
if(cnt%2){
if(!a)a=j;
else b=j;
}
}
}
if(x!=1){
if(!a)a=x;
else b=x;
}
if(!a){//如果本身就是完全平方数,则答案为1
puts("1");
return 0;
}
add(a,b,idx);
add(b,a,idx);
idx++;
}
for(int i=1;i<=1000;i++){//max(a[i])=1e6 -> sqrt(max(a[i]))=1e3
if(isprime(i)){
bfs(i);
}
}
if(ans==1e9)ans=-1;
printf("%lld\n",ans);
return 0;
}