原题链接
题目描述
我没办法写出更短的意思了QwQ,洛谷太强了Orz。在此只能引用:
给一些数,每个的因数个数不超过 7 7 7,求最少选出多少个,使得乘积为完全平方。无解输出 − 1 -1 −1。
解题思路
有两个突破点,否则你推死也退不出来。
先把每个数分解质因数。如果某个质因数的次数是偶数那么就忽略,因为平方数每个质因数的次数就是偶数,反之就要去寻找哪个其他的数的质因数的次数也是奇数,两个一乘就变成偶数了。但是数的个数太多了,如果枚举肯定 T L E TLE TLE。那么我们就上第一个突破点:
最多有 7 7 7个因数
说明每个数最多有两个不同的质因数,因为如果有三个不同的质因数那么根据因数定理,就有 2 3 = 8 2^3=8 23=8个因数,大于 7 7 7个。只有两个质因数,很像一条边连接了两个点,所以可以把每个质因数当成点,然后两个不同的质因数连一条无向边。因为我们要处理的是次数是奇数的质因数,所以如果有次数是偶数的质因数就用 1 1 1代替这个位置。考虑有了这个图之后怎么计算是否可行,假设选择一条边就说明选了这个数,如果想要是完全平方数,则每个次数为奇数的质因子就一定选了偶数次,那么选出来的图中每个点的度数都是偶数:就是环啊!所以本题就变成了找一个最小环。因为我们要判断图是否连通,所以要枚举从每一个质数出发,用 b f s bfs bfs找环。但是质数很多,又要 T L E TLE TLE。我们来上第二个突破点:
每个数最大为 1 0 6 10^6 106
这个感觉就是一个普通的数据范围,但是并不简单。我们知道每个数 n n n都不会有两个及以上的质因数是大于 n \sqrt n n的,因为如果有两个及以上,那么就有两个数乘起来大于 n n n,但是它应当是 n n n的因数,比 n n n小,矛盾。所以,这样建图,每条边连接的两个点都至少有一个在 1000 1000 1000以内的。所以枚举的起点在 1000 1000 1000以内就够了,更大的就一定已经遍历过了,没有必要再遍历了。因为质数有点大,所以要离散化所有质数。
丑陋代码
#include<bits/stdc++.h>
using namespace std;
const int NN=1e6+4;
int prime[NN],num[NN],head[NN],e[NN*2],ne[NN*2],idx,d[NN];
bool vis[NN];
void add(int u,int v)
{
e[++idx]=v;
ne[idx]=head[u];
head[u]=idx;
}
int main()
{
int cnt=0,maxs=1;
num[1]=1;
for(int i=2;i<NN;i++)
{
if(!vis[i])
{
prime[++cnt]=i;
num[i]=cnt+1;
if(i<1000)
maxs++;
}
for(int j=1;i*prime[j]<NN;j++)
{
vis[i*prime[j]]=true;
if(!(i%prime[j]))
break;
}
}
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int x,q=1,p=1;
scanf("%d",&x);
for(int i=1;prime[i]*prime[i]<=x;i++)
{
int res=0;
while(!(x%prime[i]))
{
res++;
x/=prime[i];
}
if(res&1)
{
if(q>1)
p=prime[i];
else
q=prime[i];
}
}
if(x>1)
{
if(q>1)
p=x;
else
q=x;
}
add(num[q],num[p]);
add(num[p],num[q]);
}
int ans=1e9;
for(int s=1;s<=maxs;s++)
{
queue<pair<int,int> >q;
q.push(make_pair(s,0));
memset(d,-1,sizeof(d));
d[s]=0;
while(q.size())
{
int u=q.front().first,fa=q.front().second;
q.pop();
for(int i=head[u];i;i=ne[i])
{
int v=e[i];
if(v==fa)
continue;
if(d[v]==-1)
{
d[v]=d[u]+1;
q.push(make_pair(v,u));
}
else
ans=min(ans,d[u]+d[v]+1);
}
}
}
if(ans>n)
printf("-1");
else
printf("%d",ans);
return 0;
}