蓝桥杯 2016省赛 最大比例 从直觉到辗转相除法的变化

蓝桥杯 2016省赛 最大比例 从直觉到辗转相除法的变化

题目链接

题面请看链接,分析一下题目,题目就是要求从一等比数列的某几项中得知,这个等比数列的最大公比。

看到题目显然是和gcd有关的,这里介绍两种方法,一种是从直觉而来的,一种是参照网上题解,自己推导而出的。

1.直觉题解

        等比数列必然有一个首项,显然要先将等比数列排序之后两两相除,以此获得一个没有首项的序列。这里有一点需要注意,就是题目中有可能会出现两个一样的数字,这个时候相除答案会是1,但是1是所有正整数的最小公因数 ,显然不是我们要找的数字,所以读入数据的时候需要去重。这里我是用set去重实现的。

        之后,获得了相邻两项的商。本来做到这里,我天真的以为只要对这些商求最大公因数就行了,事实上如果存在1、8、80这样的序列,求商后得8、10,如果简单得求最大公因数会得到错误答案8。

        一开始我也没考虑到,因为如果公比为8,就无法从序列中得到由8变为80的情况。

        这是为什么?本质上是因为没有没有考虑到公比之间的变化,那如何考虑公比之间的变化。以上面的数据举例,如果存在q,则必然存在 q n 1 = 8 q^{n1}=8 qn1=8, q n 2 = 10 q^{n2}=10 qn2=10,如果考虑n1,n2的存在性情况,也就是说需要满足 q n 2 − n 1 = 4 5 q^{n2-n1}=\frac{4}{5} qn2n1=54,其中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,那么 b ∗ n + c = a b*n+c=a bn+c=a, , c = a − b ∗ n c=a-b*n c=abn,已知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,

  1. 如果存在d使得b%d= =0和c%d==0同时成立,则a%d= =0和b%d= =0也成立。
  2. 如果存在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 a q^{a} qa q b q^b qb如何求q?设q=gcq(q^a, q^b),q是其最大公比,借鉴上面的思想,找到一个相同答案但是规模更小的递归问题即可。显然,两者相除得到 q a − b q^{a-b} qab,如果对其和 q b q^b qb求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

	

你可能感兴趣的:(python,算法)