题目描述:
大小为n的集合
a
a
a,求最大的
g
g
g满足g能够整除a中大于等于n/2个数。
n<=106,a<=1012
题目分析:
每个数能够被g整除的概率>=
1
2
\frac 12
21,那么我们在a中随机一个x,随机k次,对每个x求出它所能得到的最大的答案,统计gcd(x,a[i])中x的每个约数的出现次数(若d[i]|d[j]则cnt[i]+=cnt[j]),如果某个约数出现次数>=n/2,则更新答案。
g不能整除x的概率<
1
2
\frac 12
21,故正确概率为
1
−
2
−
k
1-2^{-k}
1−2−k,复杂度为nlogn+分解约数+约数个数平方,k=10左右即可。
Code:
#include<bits/stdc++.h>
#define maxn 1000005
#define LL long long
using namespace std;
int n;
LL a[maxn],d[maxn],cnt,num[maxn],ans;
LL gcd(LL a,LL b){return b?gcd(b,a%b):a;}
int main()
{
srand(19260817);
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%I64d",&a[i]);
int k=12;
while(k--){
LL x=a[(rand()<<15|rand())%n+1]; cnt=0;
for(LL i=1;i*i<=x;i++) if(x%i==0) {d[++cnt]=i,i*i!=x&&(d[++cnt]=x/i);}
sort(d+1,d+1+cnt);
for(int i=1;i<=cnt;i++) num[i]=0;
for(int i=1;i<=n;i++) num[lower_bound(d+1,d+1+cnt,gcd(x,a[i]))-d]++;
for(int i=cnt;i>=1;i--){
int tmp=0;
for(int j=i+1;j<=cnt;j++)
if(d[j]%d[i]==0) tmp+=num[j];
if((num[i]+tmp)*2>=n) {ans=max(ans,d[i]);break;}
}
}
printf("%I64d\n",ans);
}
上面的代码中统计次数的过程可以优化为约数个数*质因数个数*log(约数个数)。
代码入下:
for(int j=1;j<=p[0];j++)
for(int i=cnt;i>=1;i--)
if(d[i]%p[j]==0)
num[lower_bound(d+1,d+1+cnt,d[i]/p[j])-d]+=num[i];
这样做并不会算重,原理参照高维前缀和,这里的质因数个数相当于维数,统计的是高维后缀和