藍橋杯 2016省賽 最大比例 從直覺到輾轉相除法的變化
題面請看鏈接,分析一下題目,題目就是要求從一等比數列的某幾項中得知,這個等比數列的最大公比。
看到題目顯然是和gcd有關的,這裏介紹兩種方法,一種是從直覺而來的,一種是參照網上題解,自己推導而出的。
1.直覺題解
等比數列必然有一個首項,顯然要先將等比數列排序之後兩兩相除,以此獲得一個沒有首項的序列。這裏有一點需要注意,就是題目中有可能會出現兩個一樣的數字,這個時候相除答案會是1,但是1是所有正整數的最小公因數 ,顯然不是我們要找的數字,所以讀入數據的時候需要去重。這裏我是用set去重實現的。
之後,獲得了相鄰兩項的商。本來做到這裏,我天真的以爲只要對這些商求最大公因數就行了,事實上如果存在1、8、80這樣的序列,求商後得8、10,如果簡單得求最大公因數會得到錯誤答案8。
一開始我也沒考慮到,因爲如果公比爲8,就無法從序列中得到由8變爲80的情況。
這是爲什麼?本質上是因爲沒有沒有考慮到公比之間的變化,那如何考慮公比之間的變化。以上面的數據舉例,如果存在q,則必然存在,,如果考慮n1,n2的存在性情況,也就是說需要滿足,其中n2-n1屬於整數。相當於對商數列求最大公因數的時候,多了這一個條件就足夠。
所以只需要在獲得商數列的基礎上,增加這些條件即可。這些條件的獲得,直接通過對商數列的排序,然後對相鄰的商進行相除即可。獲得條件後,其實條件和商數列形式上是完全一樣的,也就是這些數值需要出現在整個數列中。
所以我們通過在商數列後面追加這些數值即可。
下面放上通過的python代碼
def gcd(a, b):
if b == 0:
return a
return gcd(b, a % b)
def lgcd(a, b):
return [gcd(a[0],b[0]),gcd(a[1],b[1])]
while True:
try:
n = int(input())
l = list(set(map(int, input().split())))
l.sort()
n = len(l)
tl = []
for i in range(n - 1):
g = gcd(l[i], l[i + 1])
tl.append((l[i + 1] // g, l[i] // g))
tl=list(set(tl))
tl.sort(key=lambda x:x[0]/x[1])
n=len(tl)
for i in range(n-1):
tl.append((tl[i+1][0]/tl[i][0],tl[i+1][1]/tl[i][1]))
g = tl[0]
for i in tl:
g = lgcd(g, i)
print('%d/%d' % (g[0], g[1]))
except:
break
類輾轉相除法
輾轉相除法 想來大家都是知道的,甚至能閉着眼睛打出來,但是大部分人並沒有看推導,所以不知其本質,筆者也是如此。
回去看了一下輾轉相除法之後,頓時靈感迸發。
首先輾轉相除法的代碼是
def gcd(a,b):
if b==0:
return a
return gcd(b,a%b)
意思也就是gcd(a,b)=gcd(b,a%b),那麼如何證明?
設a%b=c,那麼, , ,已知a和b存在最大公約數m,那麼右邊一定可以被m整除,所以c一定能被m整除。
現在假設存在約數(注意不是最大公約數)d使得b%d= =0和(a%b)%d= =0同時成立,那麼a%b=kd,a=nb+kd,右邊能被d整除,所以a能被b整除。得到結論,如果存在d使得b%d= =0和(a%b)%d==0同時成立,那麼a%d= =0和b%d= =0也成立。
根據上述推論,設a%b=c,
- 如果存在d使得b%d= =0和c%d==0同時成立,則a%d= =0和b%d= =0也成立。
- 如果存在d使得a%d= =0和b%d= =0成立,則b%d= =0和c%d==0成立
通過上兩條性質,可知a,b公因子和b,c公因子完全相同。故最大公因數也相同。
至此得證gcd(a,b)=gcd(b,a%b)
如果我們假設a>b,那麼每次遞歸gcd時,第一個參數一定在變小,而第二個參數因爲%b所以,第二個參數的最大值也在變小啊,問題規模一直在變小。直到能夠處理爲止,大致思路是如此,具體細節不再稱述。
現在是已知和如何求q?設q=gcq(q^a, q^b),q是其最大公比,借鑑上面的思想,找到一個相同答案但是規模更小的遞歸問題即可。顯然,兩者相除得到,如果對其和求gcq,問題規模變小了,但是答案仍是一樣。
遞歸出口在哪?顯然如果一直遞歸,參數一直都會是q的冪次,除非在規模很小的時候,兩個參數相等了,也就是下一次遞歸時,一個參數爲1,那麼遞歸結束。返回非一參數即可。注意此處根據題意假設,公比大於1,如想得到更一般的情況,還需更多的考慮。
轉換成代碼也就是
def gcq(a, b):
if a < b:
t = a;a = b;b = t
if b == 1:
return a
return gcq(b, a // b)
上面是q爲整數版本,此題是分數,存在list裏面分子在0位置,分母在1位置
def fgcq(a, b):
# print(a,b)
if a[0] / a[1] < b[0] / b[1]:
t = a;a = b;b = t
if b[0] == 1 and b[1] == 1:
return a
return fgcq(b, [a[0] // b[0], a[1] // b[1]])
最後完整代碼
def gcq(a,b):
if a<b:
t=a;a=b;b=t;
if b==1:
return a
return gcq(b,a//b)
def fgcq(a,b):
#print(a,b)
if a[0]/a[1]<b[0]/b[1]:
t=a;a=b;b=t;
if b[0]==1 and b[1]==1:
return a
return fgcq(b,[a[0]//b[0],a[1]//b[1]])
def gcd(a,b):
if b==0:
return a
return gcd(b,a%b)
while True:
try:
n=int(input())
l=list(set(map(int,input().split())))
l.sort()
n=len(l)
tl=[]
for i in range(n-1):
g=gcd(l[i],l[i+1])
tl.append([l[i+1]//g,l[i]//g])
g=tl[0]
for i in tl:
g=fgcq(g,i)
print('%d/%d'%(g[0],g[1]))
except:
break