cf 641 A
证明
给定一个正整数序列 a={a1,a2,…,an} (n≥2)。
定义多重集合 t={lcm(ai,aj)∣1≤i<j≤n},即所有不同元素对的最小公倍数 (LCM) 的集合。
我们的目标是计算 g=gcd(t),即集合 t 中所有元素的最大公约数 (GCD)。
为了方便证明,我们定义:
- gk=gcd({lcm(ak,ai)∣1≤i≤n,i=k}),即所有涉及 ak 的 LCM 对的 GCD。
- dk=gcd({ai∣1≤i≤n,i=k}),即除了 ak 外所有其他 ai 的 GCD。
证明 1:g=gcd(g1,g2,…,gn)
我们需要证明 g 与 G=gcd(g1,g2,…,gn) 相等。我们将通过证明 g∣G 和 G∣g 来完成。
部分 1:证明 g∣G
- 根据 g=gcd(t) 的定义,g 必须整除集合 t 中的每一个元素。即 ∀1≤i<j≤n, g∣lcm(ai,aj)。
- 考虑任意固定的 k (1≤k≤n)。对于所有 i=k,g 必须整除 lcm(ak,ai) (如果 i<k,则由步骤 1 直接得到;如果 i>k,也由步骤 1 直接得到)。
- 因为 g 整除集合 {lcm(ak,ai)∣i=k} 中的每一个元素,所以根据 GCD 的性质,g 必须整除这些元素的 GCD。即 g∣gcd({lcm(ak,ai)∣i=k})。
- 根据 gk 的定义,这意味着 g∣gk。
- 由于 g 整除 gk 对于所有的 k=1,…,n 都成立,所以 g 必须整除 g1,g2,…,gn 的最大公约数。
- 因此,g∣gcd(g1,g2,…,gn),即 g∣G。
部分 2:证明 G∣g
- 设 G=gcd(g1,g2,…,gn)。根据 GCD 的定义,G 整除每一个 gk。
- 考虑任意 k。我们知道 G∣gk 且 gk=gcd({lcm(ak,ai)∣i=k})。这意味着 G 必须整除集合 {lcm(ak,ai)∣i=k} 中的每一个元素。即 ∀i=k, G∣lcm(ak,ai)。
- 现在考虑集合 t 中的任意元素 lcm(ai,aj),其中 i<j。
- 根据步骤 2,因为 G∣gi,所以 G 整除所有涉及 ai 的 LCM 对,特别地,G∣lcm(ai,aj) (因为 j=i)。
- 由于 G 整除集合 t 中的 每一个 元素 lcm(ai,aj),所以根据 GCD 的定义,G 必须整除集合 t 中所有元素的 GCD。
- 因此,G∣gcd(t),即 G∣g。
结论 1:
因为 g∣G 且 G∣g,并且 g,G 都是正整数(GCD 总是正的),所以 g=G。
即:gcd(t)=gcd(g1,g2,…,gn)。
证明 2:gk=lcm(ak,dk)
我们需要证明 gcd({lcm(ak,ai)∣i=k})=lcm(ak,gcd({ai∣i=k}))。
我们将使用 GCD 和 LCM 的一个重要的(类似分配律的)性质:
对于任意正整数 X,Y1,…,Ym,有:
gcd(lcm(X,Y1),lcm(X,Y2),…,lcm(X,Ym))=lcm(X,gcd(Y1,Y2,…,Ym))
证明该性质(使用素数幂分解):
令任意素数 p。设 X=pα⋅X′, Yj=pβj⋅Yj′,其中 X′,Yj′ 不含素因子 p。
- 左侧 (LHS) 的 p 的指数是:
min(max(α,β1),max(α,β2),…,max(α,βm))
- 右侧 (RHS) 的 p 的指数是:
max(α,min(β1,β2,…,βm))
我们需要证明 minj(max(α,βj))=max(α,minj(βj))。
设 βmin=minj(βj)。
- 情况 1: 如果 α≤βmin。
- LHS: max(α,βj)≥α 且 max(α,βj)≥βj≥βmin。因为 α≤βmin,所以对于某个 j′ 使得 βj′=βmin,我们有 max(α,βj′)=βmin。对于其他的 βj≥βmin,有 max(α,βj)≥βmin。所以 minj(max(α,βj))=βmin。
- RHS: max(α,minj(βj))=max(α,βmin)=βmin (因为 α≤βmin)。
- 两侧相等。
- 情况 2: 如果 α>βmin。
- LHS: max(α,βj)。因为 α>βmin,所以对于某个 j′ 使得 βj′=βmin,我们有 max(α,βj′)=α。对于其他的 βj≥βmin,如果 βj≥α,则 max(α,βj)=βj≥α;如果 βj<α,则 max(α,βj)=α。因此,所有 max(α,βj) 的值都 ≥α,且至少有一个等于 α。所以 minj(max(α,βj))=α。
- RHS: max(α,minj(βj))=max(α,βmin)=α (因为 α>βmin)。
- 两侧相等。
因此,该性质对于任意素数 p 的指数都成立,故性质本身成立。
应用该性质:
在我们的问题中,令 X=ak,令集合 {Y1,…,Ym} 为 {ai∣i=k}。
那么根据该性质:
gcd({lcm(ak,ai)∣i=k})=lcm(ak,gcd({ai∣i=k}))
根据 gk 和 dk 的定义,上式即为:
gk=lcm(ak,dk)
证明完毕。
最终结论
结合两个证明:
- 我们需要计算 g=gcd(t)。
- 我们证明了 g=gcd(g1,g2,…,gn)。
- 我们证明了 gk=lcm(ak,dk),其中 dk=gcd(ai∣i=k)。
因此,最终的计算方法是:
g=gcd(lcm(a1,d1),lcm(a2,d2),…,lcm(an,dn))
其中 dk 可以通过预计算前缀和后缀 GCD 来高效获得。
参考代码 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| vector<long long> prefix(n); vector<long long> suffix(n); prefix[0] = arr[0]; for(int i = 1; i < n; ++i) { prefix[i] = std::gcd(prefix[i - 1], 1LL * arr[i]); } suffix[n - 1] = arr[n - 1]; for(int i = n - 2; i >= 0; --i) { suffix[i] = std::gcd(suffix[i + 1], 1LL * arr[i]); }
long long ans = 0; for(int i = 0; i < n; ++i) { long long k; if(i == 0) { k = suffix[1]; } else if(i == n - 1) { k = prefix[n - 2]; } else { k = std::gcd(prefix[i - 1] , suffix[i + 1]); }
k = std::lcm(1LL * arr[i], k); if(k != 0) { ans = std::gcd(ans, k); } else { ans = k; } }
cout << ans << nl;
|
下面我们换一种思路来理解
usaco guide
先看代码 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| long long n , a , b; input(n, a, b);
auto opt = [&]() { long long t = std::gcd(a,b); std::tie(a,b) = std::make_pair(t , a / t * b); };
opt(); for(int i = 2; i < n; ++i) { long long t; input(t); b = std::gcd(b, t); opt(); }
cout << b << nl;
|
问题分析:对中最小公倍数的最大公约数
问题描述
给定一个包含 n 个正整数的序列 a={a1,a2,…,an},其中 n≥2。
定义一个多重集 t={lcm(ai,aj)∣1≤i<j≤n}。
目标是计算 G=gcd(t)。
换句话说,我们需要计算:
G=gcd({lcm(ai,aj)∣1≤i<j≤n})
算法概述(迭代方法)
维护两个变量,我们称之为 g
和 l
(对应代码中的 a
和 b
)。
在处理完前 k 个元素 {a1,…,ak} 后(其中 k≥2),这两个变量存储以下值:
g
= Gk=gcd(a1,a2,…,ak)
l
= Tk=gcd({lcm(ai,aj)∣1≤i<j≤k})
算法流程如下:
- 初始化(使用 a1,a2):计算 G2=gcd(a1,a2) 和 T2=lcm(a1,a2)。将它们存入
g
和 l
。
- 循环(从 k=3 到 n):读取 ak。使用前一步的 Gk−1、Tk−1 和新元素 ak 来更新
g
为 Gk,l
为 Tk。
- 结果:处理完 an 后,存储在
l
中的值即为最终答案 Tn。
正确性证明
我们使用数学归纳法来证明该算法在每一步 k 都能在变量 l
中正确计算出 Tk。
定义
- Gk=gcd(a1,…,ak)
- Tk=gcd({lcm(ai,aj)∣1≤i<j≤k}) (我们在第 k 步的目标值)
- Sk=gcd({lcm(ak,ai)∣1≤i<k}) (涉及新元素 ak 的所有配对的 lcm 的 gcd)
Tk 的递推关系
满足 1≤i<j≤k 的数对 (i,j) 可以分为两组:
- j<k 的数对:{(i,j)∣1≤i<j≤k−1}
- j=k 的数对:{(i,k)∣1≤i<k}
根据 gcd 的性质 gcd(A∪B)=gcd(gcd(A),gcd(B)),我们得到:
Tk=gcd(Tk−1gcd({lcm(ai,aj)∣1≤i<j≤k−1}),Skgcd({lcm(ak,ai)∣1≤i<k}))
Tk=gcd(Tk−1,Sk)
关键恒等式
我们可以证明(通过素数分解和指数上的 min/max 运算):
Sk=gcd({lcm(ak,ai)∣1≤i<k})=lcm(ak,gcd({ai∣1≤i<k}))
Sk=lcm(ak,Gk−1)
组合递推公式
将上述恒等式代入 Tk 的递推关系:
Tk=gcd(Tk−1,lcm(ak,Gk−1))
这就是算法在第 k 步需要为变量 l
计算出的目标值。
分析代码的更新步骤
令 gk−1 和 lk−1 为处理 ak 之前的值。根据归纳假设,gk−1=Gk−1 且 lk−1=Tk−1。代码执行:
- 读取 t=ak。
- 计算中间值 ltemp=gcd(lk−1,t)=gcd(Tk−1,ak)。
- 调用 opt(gk−1,ltemp)。设新值为 gk,lk。
- gk=gcd(gk−1,ltemp)=gcd(Gk−1,gcd(Tk−1,ak))
- lk=lcm(gk−1,ltemp)=lcm(Gk−1,gcd(Tk−1,ak))
我们需要验证:
-
验证 gk=Gk:
我们需要证明 gcd(Gk−1,gcd(Tk−1,ak))=gcd(Gk−1,ak)。
这个等式成立,因为 Gk−1∣ai (对于 i<k),这意味着 Gk−1∣lcm(ai,aj) (对于 i<j<k),因此 Gk−1∣Tk−1。令 vp(⋅) 表示素数 p 的指数。则 vp(Gk−1)≤vp(Tk−1)。
vp(左)=min(vp(Gk−1),min(vp(Tk−1),vp(ak)))=min(vp(Gk−1),vp(ak)) (因为 vp(Gk−1)≤vp(Tk−1))。
vp(右)=min(vp(Gk−1),vp(ak))。
由于所有素数 p 的指数都匹配,等式成立。因此 gk=Gk。
-
验证 lk=Tk:
我们需要证明 lcm(Gk−1,gcd(Tk−1,ak))=gcd(Tk−1,lcm(ak,Gk−1))。
令 g=vp(Gk−1),t=vp(Tk−1),v=vp(ak)。我们需要证明 max(g,min(t,v))=min(t,max(v,g))。
我们已知 g≤t。当 g≤t 时,这个关于非负整数的恒等式成立。它是整数在整除关系构成的格(Lattice)上的分配律的一种形式(其中 lcm ↔ max,gcd ↔ min 对应于指数运算)。
由于所有素数 p 的指数都匹配,等式成立。因此 lk=Tk。
基础情况
对于 k=2,代码计算 g=gcd(a1,a2)=G2 和 l=lcm(a1,a2)=T2。基础情况成立。
结论
通过数学归纳法,我们证明了在处理完 ak 后,变量 l
正确地存储了值 Tk。因此,在处理完 an 后,l
存储了 Tn,即为题目所求的答案 G。