Description
给定\(n\)个正整数,求出\(gcd(\{lcm({ai,aj}) | i<j\})\)
Input
第一行一个整数\(n\)
第二行\(n\)个整数
Output
输出答案
Analysis
一条属实有点意思的题目。
结束以后听大佬说是分解定理做,寻思半天没琢磨透,后来还是走的暴力+优化路线。
不妨先看暴力代码
#include <bits/stdc++.h> using namespace std; typedef long long ll; int a[100005]; int main() { //freopen("","r",stdin); //freopen("","w",stdout); int n; scanf("%d", &n); int ans = 0; for (int i = 0; i < n; i++) { scanf("%d", &a[i]); for (int j = 0; j < i; j++) { int lcm = (a[j] * a[i]) / __gcd(a[j], a[i]); if (ans == 0) { ans = lcm; } else { ans = __gcd(ans, lcm); } } } printf("%d", ans); return 0; }
算法的复杂度卡在两个for手上,每一次就\(a[i]\)与之前所有数的\(lcm\)时运算过多,考虑优化。可以发现的是\(a[i]\)与之前所有数的\(lcm\)再求\(gcd\)与\(a[i]\)与之前所有数求\(gcd\)再\(lcm\)没有区别
准确的说,对\(a,b,c\)可以证明
$$gcd(lcm(a,c),lcm(b,c))=lcm(gcd(a,b),c)$$
证明较为巧妙,提供如下
需要首先得到引理1
由唯一分解定理可知,任何一个数都可被分解成若干个素数的幂积
我们对一素数\(p\)讨论,有
$$lcm(p^a,p^b)=p^{max(a,b)}$$
$$gcd(p^a,p^b)=p^{min(a,b)}$$
接下来我们需要得到引理2
$$max(min(a,b),c)=min(max(a,c),max(b,c))$$
由对称性不妨假设\(a<=b\),则\(max(a,c)<=max(b,c)\)易得上结论成立
相信聪明的你已经发现了引理2实质上是对需证明结论对单一素数情况下的特殊情况
我们对一素数\(p\)讨论
原式则有
$$gcd(lcm(p^a,p^c),lcm(p^b,p^c))$$
$$=min(max(p^a,p^c),max(p^b,p^c))$$
$$=max(min(p^a,p^b),c)$$
$$=lcm(gcd(p^a,p^b),p^c)$$
$$Q.E.D$$
于是,我们就可以对求lcm的过程做前缀gcd的优化
即可得解。
Accepted Code
/* * @Author: Gehrychiang * @LastEditTime: 2020-05-13 01:29:58 * @Website: www.yilantingfeng.site * @E-mail: gehrychiang@aliyun.com * @ProbTitle: (记得补充题目标题) */ #pragma GCC optimize(3) #include <bits/stdc++.h> using namespace std; typedef long long ll; ll a[100005]; ll gcdd[100005]; int main() { //freopen("","r",stdin); //freopen("","w",stdout); int n; scanf("%d", &n); ll ans = 0; for (int i = 0; i < n; i++) { scanf("%lld", &a[i]); if (i == 0) { gcdd[0] = a[0]; } else { gcdd[i] = __gcd(gcdd[i - 1], a[i]); ll lcm = (a[i] / __gcd(a[i], gcdd[i - 1]) * gcdd[i - 1]); if (ans == 0) { ans = lcm; } else { ans = __gcd(ans, lcm); } } } printf("%lld", ans); return 0; }