关于某道gcd题暴力优化的研究

3 篇文章 0 订阅
2 篇文章 0 订阅

题目描述
有 n 个数字 a[1],a[2]…a[n]。
求 max{gcd(ai,aj)} ( i!=j ) 。
n ≤ 10000 n \leq 10000 n10000
a i ≤ 1000000 a_i \leq 1000000 ai1000000
这题std很好想,如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n;
int d[1000005];
int main(){
	freopen("gcd.in","r",stdin);
	freopen("gcd.out","w",stdout);
	scanf("%d",&n);
	for(int i=1,x;i<=n;i++) scanf("%d",&x),d[x]++;
	int ans=0;
	for(int i=1000000;;i--){
		int num=0;
		for(int j=1;j*i<=1000000;j++){
			num+=d[j*i];
		}
		if(num>1) {
			ans=i;
			break;
		}
	}
	printf("%d\n",ans);
	return 0;
}

但正如题目写的,我们要研究暴力的优化。
这是普通暴力:

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define N 10010
using namespace std;

int gcd(int a,int b) {
	return (b==0 ? a : gcd(b,a%b));
}

inline bool cmp(const int &x,const int &y) {
	return x>y;
}

int n,a[N];

int main() {
	freopen("gcd.in","r",stdin);
	freopen("gcd.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;i++) {
		scanf("%d",&a[i]);
	}
	register int ans=0;
	for(int i=1;i<n;i++) {
		for(register int j=i+1;j<=n;j++) {
			ans=max(ans,gcd(a[i],a[j]));
		}
	}
	printf("%d\n",ans);
	return 0;
}

直接暴力显然是过不了的。

但是,我们可以先从大到小排序,然后发现当 a i a_i ai a j a_j aj a n s ans ans小时,一定不是最优解,而且之后的数都不会比 a n s ans ans大,所以我们可以直接退出。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define N 10010
using namespace std;

int gcd(int a,int b) {
	return (b==0 ? a : gcd(b,a%b));
}

inline bool cmp(const int &x,const int &y) {
	return x>y;
}

int n,a[N];

int main() {
	freopen("gcd.in","r",stdin);
	freopen("gcd.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;i++) {
		scanf("%d",&a[i]);
	}
	register int ans=0;
	sort(a+1,a+n+1,cmp);
	for(int i=1;i<n;i++) {
		if(a[i]<=ans) break; //卡常1
		for(register int j=i+1;j<=n;j++) {
			if(a[j]<=ans) break;  //卡常1
			ans=max(ans,gcd(a[i],a[j]));
		}
	}
	printf("%d\n",ans);
	return 0;
}

这样写对随机数据表现很好,可以跑到 0.01 0.01 0.01 秒左右,主要是因为很容易出现相同的数,使答案大于 900000 900000 900000 。但是如果我们设计一个没有相同 a i a_i ai 的数据,就会被卡到一秒以外。

于是,一位天才的卡常选手加了一个优化。他将 g c d gcd gcd 改为如下形式:

int gcd(int a,int b) {
	if(a<=ans) return 1;  //相当简单的一句话,但是可以把时间卡到0.3秒左右
	return (b==0 ? a : gcd(b,a%b));
}

但是还不够好,暴力就是要碾标算才能体现它的价值。
通过多次实验发现,没有重复数据时,答案在 400000 − 500000 400000-500000 400000500000之间,显然是出现了一个 a i &gt; 400000 a_i&gt;400000 ai>400000,且存在 a j = 2 ∗ a i a_j = 2*a_i aj=2ai,于是针对这种数据进行玄学:

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define N 10010
using namespace std;

int n,a[N],v[1000010],ans;

int gcd(int a,int b) {
	if(a<=ans) return 1;
	return (b==0 ? a : gcd(b,a%b));
}

inline bool cmp(const int &x,const int &y) {
	return x>y;
}

inline int max(int x,int y) {
	return x>y ? x : y;
}

int main() {
	freopen("gcd.in","r",stdin);
	freopen("gcd.out","w",stdout);
	scanf("%d",&n);
	ans=0;
	memset(v,0,sizeof v);
	for(int i=1;i<=n;i++) {
		scanf("%d",&a[i]);
		v[a[i]]++;
		if(v[a[i]]>1 && a[i]>ans) ans=a[i];
	}
	sort(a+1,a+n+1,cmp);
	for(int i=500000;i>=1;i--) {
		if(ans>i) break;
		if(v[i]>0 && v[i*2]>0 && ans<i) {
			ans=i;		//玄学出奇迹
			break;
		}
	}
	for(int i=1;i<n;i++) {
		if(a[i]/2<=ans) break;   //注释1
		for(register int j=i+1;j<=n;j++) {
			if(a[j]<=ans) break;
			ans=max(ans,gcd(a[i],a[j]));
		}
	}
	printf("%d\n",ans);
	return 0;
}

注释1:在这里 a i a_i ai表示较大的数,如果 a i a_i ai出现了两次以上,则可以退出;如果只有一个 a i a_i ai,则 gcd ⁡ ( a i , a j ) \gcd (a_i,a_j) gcd(ai,aj) 不会超过 a i / 2 a_i/2 ai/2。这也是一个比较有用的优化。

这个程序耗时在 0.01 s 0.01s 0.01s 左右,与标算相似,超过 O ( n ∗ n ) O(n* \sqrt n) O(nn ) 算法,这可以算是玄学卡常的阶段性胜利。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值