[codeforces 1350C] Orac and LCM 最大公约数+最小公倍数+公式推导

Codeforces Round #641 (Div. 2)   参与排名人数11937

[codeforces 1350C]   Orac and LCM   最大公约数+最小公倍数+公式推导

总目录详见https://blog.csdn.net/mrcrack/article/details/103564004

在线测评地址http://codeforces.com/contest/1350/problem/C

ProblemLangVerdictTimeMemory
C - Orac and LCM GNU C++17Accepted46 ms6000 KB

数论中,一个很简单的公式,若不知道的情况下,真是弄得够呛。

通过打比赛,了解数论中的公式,也是一个不错的选择。

公式推导如下

a1 a2 a3 a4

a1*a2代表LCM(a1,a2)
GCD(a1*a2,a1*a3,a1*a4)
=a1*GCD(a2,a3,a4)
即
求a1与GCD(a2,a3,a4)的最小公倍数c1

GCD(a2*a3,a2*a4)
=a2*GCD(a3,a4)
即
求a2与GCD(a3,a4)的最小公倍数c2

GCD(a3*a4)
=a3*GCD(a4)
即
求a3与GCD(a4)的最小公倍数c3

最后再求GCD(c1,c2,c3)

证明如下(以下证明,若读者看不下去,可以直接跳到AC代码):

目标是证明:GCD(LCM(a1,a2),LCM(a1,a3),LCM(a1,a4))==LCM(a1*GCD(a2,a3,a4))

a1 a2 a3 a4

a1*a2代表LCM(a1,a2)
GCD(a1*a2,a1*a3,a1*a4)
=a1*GCD(a2,a3,a4)
即
求a1与GCD(a2,a3,a4)的最小公倍数c1


请注意:下面证明过程中的*仅代表乘号,重要的事情说三遍,*仅代表乘号,*仅代表乘号。
证明过程如下:
LCM(a1,a2)=a1*k2*d,a2=a12*k2*d,注意a12是a1里面的因子,
LCM(a1,a3)=a1*k3*d,a3=a13*k3*d,注意a13是a1里面的因子,
LCM(a1,a4)=a1*k4*d,a4=a14*k4*d,注意a14是a1里面的因子,

请注意,GCD(k2,k3,k4)=1

GCD(LCM(a1,a2),LCM(a1,a3),LCM(a1,a4))
=GCD(a1*k2*d,a1*k3*d,a1*k4*d)
=a1*d*GCD(k2,k3,k4)
因GCD(k2,k3,k4)=1
上式变为a1*d


LCM(a1*GCD(a2,a3,a4))
=LCM(a1*GCD(a12*k2*d,a13*k3*d,a14*k4*d)
=LCM(a1*d*GCD(a12*k2,a13*k3,a14*k4)

因GCD(k2,k3,k4)=1
GCD(k2,a13)==a1中的因子
GCD(k2,a14)==a1中的因子
上式变为
LCM(a1*d*GCD(a12,a13*k3,a14*k4))

因GCD(k2,k3,k4)=1
GCD(k3,a12)==a1中的因子
GCD(k3,a14)==a1中的因子
上式变为
LCM(a1*d*GCD(a12,a13,a14*k4))

因GCD(k2,k3,k4)=1
GCD(k4,a12)==a1中的因子
GCD(k3,a13)==a1中的因子
上式变为
LCM(a1*d*GCD(a12,a13,a14))

请注意,因为a12是a1里面的因子,a13是a1里面的因子,a14是a1里面的因子
故
LCM(a1*GCD(a12,a13,a14))=a1
因此式子LCM(a1*d*GCD(a12,a13,a14))变为LCM(a1*d)=a1*d

故
GCD(LCM(a1,a2),LCM(a1,a3),LCM(a1,a4))==
LCM(a1*GCD(a2,a3,a4))

结论得证。

AC代码如下

#include <stdio.h>
#define LL long long
#define maxn 100010
LL a[maxn],b[maxn],c[maxn],ans;
LL GCD(LL a,LL b){//最大公约数
	return b?GCD(b,a%b):a;
}
LL LCM(LL a,LL b){//最小公倍数
	return a*b/GCD(a,b);
}
int main(){
	int n,i;
	scanf("%d",&n);
	for(i=1;i<=n;i++)scanf("%lld",&a[i]);
	b[n]=a[n];
	for(i=n-1;i>=1;i--)b[i]=GCD(b[i+1],a[i]);//b[1]=GCD(a1,a2,a3,......,an)
	for(i=1;i<n;i++)c[i]=LCM(a[i],b[i+1]);//c[1]=LCM(a[1],GCD(a2,a3,...,an))
	ans=c[1];
	for(i=2;i<n;i++)ans=GCD(ans,c[i]);//ans=GCD(c[1],c[2],......,c[n-1])
	printf("%lld\n",ans);
	return 0;
}

方法二:根据质因数出现频率找留存质因数

ProblemLangVerdictTimeMemory
C - Orac and LCM GNU C++17Accepted140 ms10100 KB

样例模拟如下

Input:
4
10 24 40 80
10=2*5
24=3*2^3
40=5*2^3
80=5*2^4
可以看到4个数中,因数5出现4-1=3次,因数5要计入结果
可以看到4个数中,因数2^3出现4-1=3次,因数2^3要计入结果
可以看到4个数中,因数3出现2次,达不到4-1=3次,因数3只好扔了
故,输出结果为5*2^3=40

Output:
40=5*2^3

AC代码如下(因C语言的写法要爆内存,引入了STL中的vector,第一次用。)

#include <cstdio>
#include <algorithm>
#include <vector>
#define LL long long
using namespace std;
vector<int> vec[20000];//20000的原因是,在[2,200000]之间质数个数不会超过18000,这可通过线性筛素数得到
int not_prime[200010],prime[20000],pn,x,a[100010],pos[200010];
void linear_shaker(int x){//线性筛素数
	int i,j;
	for(i=2;i<=x;i++){
		if(!not_prime[i])pn++,prime[pn]=i,pos[i]=pn;//pos[i]=pn标记质数i在prime[]中的位置
		for(j=1;i*prime[j]<=x;j++){
			not_prime[i*prime[j]]=1;
			if(i%prime[j]==0)break;
		}
	}
}
void divide(int x){//将x进行质因数分解
	int i;
	for(i=2;i*i<=x;i++)
		if(x%i==0){
			int cnt=0;//cnt不会超过20,理由:2^20=10^6>200000
			while(x%i==0)x/=i,cnt++;//cnt统计i的幂次
			vec[pos[i]].push_back(cnt);//引入vec[]实属无奈,考虑了半天,C语言要爆内存,暂时找不到好办法。
		}
	if(x>1)vec[pos[x]].push_back(1);//x>1表示该数是素数
}
int main(){
	int n,i,j;
	LL ans=1;
	scanf("%d",&n);
	for(i=1;i<=n;i++)scanf("%d",&a[i]),x=max(x,a[i]);
	linear_shaker(x);
	for(i=1;i<=n;i++)divide(a[i]);
	for(i=1;i<=pn;i++)
		if(vec[i].size()==n-1){//若有n-1个数有雷同的质因数
			sort(vec[i].begin(),vec[i].end());
			for(j=1;j<=vec[i][0];j++)ans*=prime[i];//取最小幂次vec[i][0]
		}else if(vec[i].size()==n){//若有n个数有雷同的质因数
			sort(vec[i].begin(),vec[i].end());
			for(j=1;j<=vec[i][1];j++)ans*=prime[i];//取第二小幂次vec[i][0]
		}
	printf("%lld\n",ans);
	return 0;
}

 

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 15
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值