题意:给你\(n\)个数,去掉尽量少的数使得剩下数的\(gcd\)比原来的大,无解输出\(-1\)
题目意思很简洁,想理出来一个清晰的思路却花了不少时间......首先先计算出总的\(GCD\),然后再把每个数都除去这个\(GCD\),接下来的事情就得仔细考虑一下了。
设\(M=max\left\{a[i]\right\}\),则若枚举\(1-M\)内所有质数(显然枚举质数比合数优)并一一判断在\(1-n\)中整除它们的数字的个数,复杂度为\(O(\frac{nM}{log\,M})\)必须爆炸。当时想到这里就优化不下去了,于是凉凉。实际上有一种看似暴力的方法可以较优地解决这个问题:我们开一个数组\(b[i]\),并在每一个\(b[a[i]]\)的位置上加一,那么当我们枚举质数的时候将其所有倍数上的数组\(b\)的值加在一起就可以更新答案了。运行次数大概是\(\sum \limits_{i为质数且i \le M}\frac{M}{i}\),这东西收敛得比较快,所以复杂度可以接受
代码如下:
#include<cstdio>
#include<iostream>
#define INF 1000000000
using namespace std;
const int N=3e5+10;
const int M=1.5e7+10;
int n,a[N],GCD,primes[M],v[M],cnt,num[M],sum,ans=INF,maxn;
inline int exgcd(int x,int y){int r;while(x&&y){r=x%y;x=y;y=r;}return x;}
inline void Primes_Table(){
for(register int i=2;i<=M-10;i++){
if(!v[i]){v[i]=i;primes[++cnt]=i;}
for(register int j=1;j<=cnt&&i*primes[j]<=M-10;j++){
if(v[i]<primes[j])continue;v[i*primes[j]]=primes[j];}
}
}
int main(){
Primes_Table();scanf("%d",&n);
for(register int i=1;i<=n;i++)scanf("%d",&a[i]);
GCD=a[1];for(register int i=2;i<=n;i++)GCD=exgcd(GCD,a[i]);
for(register int i=1;i<=n;i++)a[i]/=GCD,maxn=max(maxn,a[i]);
for(register int i=1;i<=n;i++)num[a[i]]++;
for(register int i=1;i<=cnt;i++){sum=0;
for(register int j=primes[i];j<=maxn;j+=primes[i])sum+=num[j];
ans=min(ans,n-sum);}
printf("%d\n",ans==n? -1:ans);return 0;
}