4 经典gcd问题
题目来源:https://vjudge.net/problem/UVA-1642
参考[1]:https://www.cnblogs.com/zhuanzhuruyi/p/6701373.html?utm_source=itdadao&utm_medium=referral
题目:给你n(n<=100000)个正整数,求一个连续子序列使序列的所有元素的最大公约数与个数乘积最大
输入:
5
30 60 20 20 20
输出:
80
gcd有三个性质:
- gcd(a, a) = a
- gcd(a, b) = gcd(b, a mod b)
- gcd(a, b, c) = gcd( gcd(a, b), c)
#求两个数的最大公约数
def gcd(a,b):
if b == 0:
return a
else:
return gcd(b,a%b)
- 用一个列表中的列表g,存储以arr[i]结尾的长度为l+1的gcd值
- 根据gcd的性质给出递推:
- g[i][l] = ( g[i-1][l-1], arr[i] )
- 复杂度是O(n^2)
eg: 对于数列a b c d e
列表g中有5个列表, 第i个列表中存储i+1个gcd值,
第0个列表为[a]
第1个列表为[b, gcd(a, b)]
第2个列表为[c, gcd(c, b), gcd(c, b, a)]
第3个列表为[d, gcd(d, c), gcd(d, c, b), gcd(d, c, b, a)]
第四个列表为~~~~
# 传入数列及长度n
def solvegcd(arr,n):
#g[i][l]存储以arr[i]结尾的长度为l+1的gcd值,0<=l<=i
g = []
g.append([arr[0]])
maxgl = arr[0]
for i in range(1,n):
#g[i][0]即长度为1时的gcd就是arr[i]自己
g.append([arr[i]])
#print(g)
for l in range(1,i+1):
il = gcd(g[i-1][l-1],arr[i])
#print(arr[i],'长度为:',l+1,il)
g[i].append(il)
maxgl = max(g[i][l]*(l+1),maxgl)
#print(maxgl)
a = [30,60,20,20,20]
solvegcd(a,5)
输出:
80
- 上面这个复杂度太高了,参考[1]的方法可以改进:
由性质3知道 k k k个数与 k + 1 k+1 k+1个数的最大公约数要么相等,要么减小并且减小至少一半。
若 a a a为当前因子,因子最小为1,那么衰减速度为 l o g ( a ) log(a) log(a)。 长度为n的序列所有子串gcd的总种类数最多只有 n ⋅ l o g ( a ) n·log(a) n⋅log(a)个。
用动态规划的思想:遍历每个数,计算以这个数为结束的gcd,通过前一次计算的最多log(a)个gcd计算出此时也是最多log(a’)个gcd
- 也就是说上面的列表g中某个列表的值为1时,后面的gcd都为1,不必再计算(不知道我这样理解对不对) 当数据很大时,优越性可以显现出来。
留个思路坑:
- 输入序列arr,按l查找:
- l=1,gcd最大的就是arr最大值arr[i]
- l=2,只找含arr[i]的连续子列gcd值,
g1 = gcd( arr[i-1], arr[i] ), g2 = (arr[i], arr[i+1]),
g1>g2: M = i, m = M-(l-1)
g1<g2:m = i, M = i+(l-1)
g=max(g1, g2) - l=3,g1 = gcd(arr[m-1], g),g2 = gcd(g, arr[M+1l])
g1>g2: m=m-1, M=m+l-1
g1<g2: M=M+1, m=M-(l-1)
g=max(g1, g2) - l=4 依次推演下去~~