题目
n(n<=1e5)个数,第i个数为ai(1<=ai<=1e6),保证ai最多有7个因子
找出这个数组的最短子序列,使得子序列内的所有元素的乘积是一个完全平方数
输出这个最短子序列的长度
思路来源
https://www.bilibili.com/video/av96374512 dls的B站讲解
https://codeforces.com/contest/1325/submission/73253372 dls的代码
题解
首先,若ai有3个质因子,则每个质因子二选一,共8种选择,说明因子>=8不成立,
以上,得出质因子个数<=2
此外,出现偶数次的素因子无意义,即与
是等价的,所以,只统计出现奇数次的素因子,
把数分为无奇数次素因子,一个奇数次素因子,两个奇数次素因子,即1,p,pq三类
①若出现1,则答案为1,特判
②若出现两个相同的p,则答案为2,特判
否则,需要找到形如ab bc ca这样的环,或者a ab bc c这样的环,
对于p*q,就将p与q连一条边,
而对于一个奇数次素因子,将p理解为1*p,将1与p连一条边,
由于p*q小的素因子一定<=1e3,所以枚举所有出现的<=1e3的素因子,bfs找到无向图的最小环
dfs由于搜索顺序的原因,不一定能找到最小环/最大环,
但bfs一定能找到每个根节点对应意义下的最小环,枚举每个根节点,复杂度O(nm)
但此题由于小的素因子的特殊性,最坏情况下为O(小于1e3的质数个数*1e5),故可行
bfs树中,对于u来说,若v已经入队,则环的大小为dep[u]+dep[v]+1
注意bfs树的写法,判掉v==fa的情形,从而找到不是前驱的访问过的点
第一种写法,开一个fa数组,避免无向图的反向边
第二种写法,记录v是由第i条边更新而来的,开一个vector<P>,第一维记录点号,第二维记录边号
由于数据范围宽松,是否离散化,均可通过此题
心得
bfs求无向图最小环,只适用于边长为1的情形,复杂度
floyd求无向图最小环,适用于有边权的情形,复杂度
代码1(fa数组+离散化)
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10,INF=0x3f3f3f3f;
#define pb push_back
int n,v,mp[N],dep[N],fa[N],cnt[N],tot,ans;
bool in[N],ok;
set<int>p;
vector<int>fac;
vector<int>e[N];
void add(int u,int v){
e[u].pb(v);
}
void bfs(int s){
queue<int>q;
q.push(s);
dep[s]=0;in[s]=1;fa[s]=-1;
int u;
while(!q.empty()){
u=q.front();q.pop();
for(int v:e[u]){
if(v==fa[u])continue;
if(in[v]){
ans=min(ans,dep[u]+dep[v]+1);
}
else{
dep[v]=dep[u]+1;
in[v]=1;
fa[v]=u;
q.push(v);
}
}
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%d",&v);
fac.clear();
for(int j=2;j*j<=v;++j){
if(v%j==0){
int x=0;
while(v%j==0){
v/=j;
x++;
}
if(x&1)fac.pb(j),p.insert(j);
}
}
if(v>1)fac.pb(v),p.insert(v);
int len=fac.size();
if(!len){
puts("1");
return 0;
}
if(len==1){
if(!mp[fac[0]])mp[fac[0]]=++tot;
add(0,mp[fac[0]]),add(mp[fac[0]],0);
cnt[mp[fac[0]]]++;
}
else{
if(!mp[fac[0]])mp[fac[0]]=++tot;
if(!mp[fac[1]])mp[fac[1]]=++tot;
add(mp[fac[0]],mp[fac[1]]),add(mp[fac[1]],mp[fac[0]]);
}
}
for(int x:p){
if(cnt[mp[x]]>=2){
puts("2");
return 0;
}
}
ans=INF;
for(int i=1;i<=1000;++i){
if(!mp[i])continue;
in[0]=0;
for(int x:p)in[mp[x]]=0;
bfs(mp[i]);
}
if(ans==INF)puts("-1");
else printf("%d\n",ans);
return 0;
}
代码2(vis数组+手写队列)
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10,INF=0x3f3f3f3f;
typedef pair<int,int>P;
#define rep(i,a,n) for(int i=(a);i<=(n);++i)
#define sci(x) scanf("%d",&(x))
#define fi first
#define se second
#define pb push_back
int n,v,dep[N],vis[N],cnt[N],ans;
int q[N];
bool ok;
set<int>p;
vector<int>fac;
vector<P>e[N];
void bfs(int s){
int t=0;
q[t++]=s;
dep[s]=0;vis[s]=-2;
int u;
rep(i,0,t-1){
u=q[i];
for(auto x:e[u]){
int v=x.fi,id=x.se;
if(vis[v]!=-1 && vis[u]!=id){//v被访问过 且v不是u的前驱
ans=min(ans,dep[u]+dep[v]+1);
}
else if(vis[v]==-1){
dep[v]=dep[u]+1;
vis[v]=id;
q[t++]=v;
}
}
}
}
int main(){
sci(n);
rep(i,1,n){
sci(v);
fac.clear();
for(int j=2;j*j<=v;++j){
if(v%j==0){
int x=0;
while(v%j==0){
v/=j;
x++;
}
if(x&1)fac.pb(j),p.insert(j);
}
}
if(v>1)fac.pb(v),p.insert(v);
int len=fac.size();
if(!len){
puts("1");
return 0;
}
if(len==1){
e[fac[0]].pb(P(1,i));
e[1].pb(P(fac[0],i));
cnt[fac[0]]++;
}
else{
e[fac[0]].pb(P(fac[1],i));
e[fac[1]].pb(P(fac[0],i));
}
}
for(int x:p){
if(cnt[x]>=2){
puts("2");
return 0;
}
}
ans=INF;
for(int i=1;i<=1000;++i){
if(e[i].empty())continue;
vis[1]=-1;
for(auto x:p)vis[x]=-1;//被更新的边号
bfs(i);
}
if(ans==INF)puts("-1");
else printf("%d\n",ans);
return 0;
}