题目描述
给定一个数组,每次操作可以选择数组中任意两个相邻的元素 x, y 并将其中的一个元素替换为 gcd(x, y) ,其中 gcd(x, y) 表示 x 和 y 的最大公约数。
请问最少需要多少次操作才能让整个数组只含 1 。
输入格式
输入的第一行包含一个整数 n ,表示数组长度。
第二行包含 n 个整数 a1, a2, · · · , an,相邻两个整数之间用一个空格分隔。
输出格式
输出一行包含一个整数,表示最少操作次数。如果无论怎么操作都无法满足要求,输出 −1 。
样例输入
3 4 6 9
样例输出
4
提示
对于 30% 的评测用例,n ≤ 500 ,ai ≤ 1000;
对于 50% 的评测用例,n ≤ 5000 ,ai ≤ 106;
对于所有评测用例,1 ≤ n ≤ 100000 ,1 ≤ ai ≤ 109。‘
PS: 一道蓝桥杯考场上AC的题目,近期重新整理并优化了代码(结合了肖佬和b站蜗神的思路后优化算法),希望有机会跟大家交流探讨,也希望我的方法能帮到需要帮助的人。若有改进意见的小伙伴请评论区留言,感激不尽!
解题思路:
分析了一下,数据范围是[1,1e5],数组长度不大,算法可选范围更多了一些,涉及到数组维护什么的,首选就是线段树了,本题有不用线段树的写法,由于笔者写惯了线段树,那么本题我也引用线段树来解决问题。
有两种不同的情况:
第一种情况:对于数组中的任意一个下标i,若有ai为1,或者说如果出现m个1(m>=1),那么让长度为n的整个数组最后只含1的最小操作次数就是为n - m。举个例子,设数组A = [1,4,6],那么我们令其数组只剩下1的操作方法为:将1分别与其他两项非1进行gcd,有n - m = 3 - 1 =2,所得2次便是最小操作次数。
第二种情况:当数组中不存在任意两个相邻的数互质的情况(相邻的两数gcd为1)那么我们可以考虑找到一对i,j,找到一个区间(ai,ai+1,....,aj),若此区间能满足gcd = 1,我们便可以在区间上来找到一个i和j(i < j),以j - i来维护最小的m就可了。到此此题解决!考场上我是写一个线段树去维护这个区间gcd,最后做一次滑动窗口便可AC本题了,代码如下:
from math import gcd,inf
m = 0
def build(k,l,r):
global m
if l == r:
tree[k] = a[l]
if tree[k] == 1:
m += 1 # 第一种情况
return
mid = l + r >> 1
build(k << 1,l,mid)
build(k << 1 | 1,mid + 1,r)
tree[k] = gcd(tree[k << 1],tree[k << 1 | 1])
def queryGCD(k,l,r,x,y):
if l == x and r == y: return tree[k]
mid = l + r >> 1
if y <= mid: g = queryGCD(k << 1,l, mid,x,y)
else:
if x > mid: g = queryGCD(k << 1 | 1,mid + 1, r ,x,y)
else: g = gcd( queryGCD(k << 1,l, mid,x,mid) ,queryGCD(k << 1|1,mid + 1, r,mid + 1,y) )
return g
def main():
global tree,a,m
n = int(input())
a = [0] + list(map(int,input().split()))
tree = [0] * (4 * n)
build(1,1,n) # 建树
if m > 0: print(n - m)
else:
m,i = inf,1
for j in range(1,n+1):
while i < j and queryGCD(1,1,n,i + 1,j) == 1: i += 1
if queryGCD(1,1,n,i,j) == 1: m = min(j - i,m)
print(n + m - 1 if m < n else -1 )
main()