【Codeforces 364D】Ghd
随机化
题目大意:
给定一个集合 $S$,要求找到一个子集 $S’$ 满足 $|S’|\geq\frac{|S|}2$,且子集中所有元素的 $\gcd$ 最大。求最大 $\gcd$。
题解:
由于答案集合大小至少为原集合的一半,所以我们随机选择一个数,其在答案集合内的概率为 $\frac 1 2$。
我们考虑选择一个数 $x$,计算出它在答案集合中时,答案最大是多少。
求出 $x$ 的所有因数。由于 $x\leq 10^{12}$,其因数个数最多只有 $6000$ 个左右。这部分复杂度 $O(\sqrt{x})$。
我们计算它和其他数的 $\gcd$,这部分复杂度为 $O(n\log C)$。然后统计其每个因数作为 $\gcd$ 的出现次数。这部分复杂度为 $O(n\log d(x))$。
我们计算 $x$ 的因数 $x’$ 作为 $\gcd$ 最多能选多少个数时,枚举因数中是 $x’$ 倍数的数,将它和它的倍数作为 $\gcd$ 的次数加起来,这些数求 $\gcd$ 能得到不小于 $x’$ 的数。然后判一下是否达到了一半即可。这部分复杂度 $O(d(x)^2)$。
上述算法求出了钦定某个数在答案中的最大答案,时间复杂度是可以接受的。
我们考虑执行 $k$ 次该算法,每次随机一个数,钦定它在答案里,再执行这个算法。
那么算法的错误率为 $\frac{1}{2^k}$。
本题共有 $115$ 个测试点,那么通过所有测试点的概率为 $(\frac{2^k-1}{2^k})^{115}$。
当 $k$ 取过小时可能会出错,当 $k$ 过大时可能会超时。当然可以加一些适当剪枝来提高效率。
我这里取 $k=12$,单组测试点的正确率为 $\frac{4095}{4096}=0.999755859375$,AC 的概率约为 $0.97231096805946504405566011472105$。
如果脸黑可以多交几发手动提高正确率
Code:
#include<iostream>
#include<random>
#include<ctime>
#include<algorithm>
using namespace std;
mt19937 mt(time(0));
const int N=1e6+5;
typedef long long LL;
inline LL gcd(LL a, LL b){
if(!a||!b)return a|b;
register int t=__builtin_ctzll(a|b);
a>>=__builtin_ctzll(a);
do{
b>>=__builtin_ctzll(b);
if(a>b){LL t=b;b=a,a=t;}
b-=a;
}while(b);
return a<<t;
}
LL ans=1;
int n;LL a[N],d[8000],tmp[8000],cnt,tmpc,tot[8000];
void work(LL x){
cnt=tmpc=0;
for(int i=1;(LL)i*i<=x;++i)
if(x%i==0){
d[++cnt]=i;
if(x/i!=i)tmp[++tmpc]=x/i;
}
while(tmpc)d[++cnt]=tmp[tmpc--];
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;++i)
cin>>a[i];
for(int T=1;T<=12;++T){
int id=mt()%n+1;
work(a[id]);
for(int i=1;i<=cnt;++i)tot[i]=0;
for(int i=1;i<=n;++i)
++tot[lower_bound(d+1,d+cnt+1,gcd(a[id],a[i]))-d];
for(int i=cnt;i;--i){
if(d[i]<ans)break;
int s=tot[i];
for(int j=i+1;j<=cnt;++j)
if(d[j]%d[i]==0)s+=tot[j];
if(s*2>=n)ans=d[i];
}
}
cout<<ans<<'\n';
return 0;
}