使用扩展欧几里得算法解决POJ1061
前段时间我在刷题的时候碰到这样一道题目,题目是这样的:
http://poj.org/problem?id=1061
Description
两只青蛙在网上相识了,它们聊得很开心,于是觉得很有必要见一面。它们很高兴地发现它们住在同一条纬度线上,于是它们约定各自朝西跳,直到碰面为止。可是它们出发之前忘记了一件很重要的事情,既没有问清楚对方的特征,也没有约定见面的具体位置。不过青蛙们都是很乐观的,它们觉得只要一直朝着某个方向跳下去,总能碰到对方的。但是除非这两只青蛙在同一时间跳到同一点上,不然是永远都不可能碰面的。为了帮助这两只乐观的青蛙,你被要求写一个程序来判断这两只青蛙是否能够碰面,会在什么时候碰面。
我们把这两只青蛙分别叫做青蛙A和青蛙B,并且规定纬度线上东经0度处为原点,由东往西为正方向,单位长度1米,这样我们就得到了一条首尾相接的数轴。设青蛙A的出发点坐标是x,青蛙B的出发点坐标是y。青蛙A一次能跳m米,青蛙B一次能跳n米,两只青蛙跳一次所花费的时间相同。纬度线总长L米。现在要你求出它们跳了几次以后才会碰面。
Input
输入只包括一行5个整数x,y,m,n,L,其中x≠y < 2000000000,0 < m、n < 2000000000,0 < L < 2100000000。
Output
输出碰面所需要的跳跃次数,如果永远不可能碰面则输出一行"Impossible"
我花费了不少时间去编写代码,但是提交后超时了。经过反思,我觉得应该是自己姿势水平不够。遂去学习了一下,认识到这道题目的思路应该是求解一个带模乘法的逆,方法就是扩展欧几里得算法。
Part Ⅰ:
一开始我是这么思考问题的,这两只青蛙跳了n次后见面有点像一个追及问题。他们有一个距离差D(distance)和速度差(每次能跳的距离不同)V(deltaV),如果青蛙A跳得步子更大那么就是A在追B,反之就是B在追A。
有了速度差,我们就可以不妨这样想。让前面一只静止且在D位置的地方,后面那只在0位置以速度V追它。那么问题就变成要求出后面那只青蛙绕着一个周长为L圆(也就是纬度线)跳多少次才能达到D这个地方。如果这时V=0那么肯定就不可能碰面了。设这个蛤蟆跳了n次,那么他跳了第n次的位置应该在
这个地方。
那么我们就可以列出方程
可以转化为如下方程,t为一个整数
我们要求这个方程的整数解(n,t)。
Part Ⅱ:
为了求解这个方程,我先要介绍一个定理。
定理 1 如果任意整数a和b不都为0,则gcd(a,b)是a与b的线性组合集{ax+by: x,y∈Z}中的最小正元素。
gcd(a,b)为整数a,b的最大公约数。证明见注释。
也就是说方程一定有整数解。
如果我们找到了这个方程的一个解,就可以知道这个方程的基础解系。同时我们也知道了如果D不能被gcd(V,L)整除那么方程就没有整数解了,也就Impossible了。因为根据定理1我们知道gcd(V,L)是V和L所能组合出的最小正整数。
那么问题来了如何找出这个方程的整数解呢?这就要用到扩展欧几里得算法了。
Part Ⅲ:
我们都知道欧几里得算法,也就是所谓的辗转相除法。它可能几种最古老的算法之一,它可以很快速求得两个整数的最大公约数。而扩展欧几里得算法可以收集欧几里得算法计算过程中的额外信息,使得我们能够找到。
先上扩展欧几里得算法的伪代码
1
2
3
4
5
6
|
1 EXTENDED - EUCLID(a,b) 2 if b = = 0 3 return (a, 1 , 0 ) 4 else (d ',x' ,y') = EXTENDED - EUCLID(b,a mod b) 5 (d,x,y) = (d ',y' ,x '-a/b*y' ) 6 return (d,x,y) |
这段代码返回一个三元组,第一个元素是最大公约数,x,y 是满足ax+by=d的解。那么为什么x,y就是这个方程的整数解,它又是怎么得到的呢
下图演示了用EXTENDED-EUCLID计算gcd(99,78)的过程。
仔细观察这张图,从最后一行看。这个递归函数于b=0结束递归,返回了(3,1,0),很明显这时的3就是最大公约数。为什么是1,0呢,很明显嘛这时a=3,b=0,x=1,y=0,很自然的就满足这个方程,你y自然也可以取别的数也是满足方程的。
注意看第四行代码三元组保存了函数返回的(d',x',y'),如果这是来组最后一层递归的话那么返回的就是(3,1,0),然后我们要求出这一层的(d,x,y),使之满足
通过刚才返回的(d',x',y')很显然满足
然后我们知道代换之
得到
简化得,第五行代码就是这么来的。
这样一层层递归函数逐渐收束,最终我们得到了
Part Ⅳ:
借助扩展欧几里得算法,我们已经可以计算出的一个整数解。我们的目标是找到最小的正整数n,n存在的条件是,原因见part Ⅱ。
方程两边同乘
得到
接下来就是通过这个方程的基础解系找到最小的n即可。
在最后一步我又踩了一次坑,那就是
的通解不是
而是至于为什么就留给你去思考了。
下面附上拙劣的C代码
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
#include<stdio.h> struct triple { long long GCD; long long x; long long y; }; struct triple ex_gcd( long long a, long long b) { struct triple temp; if (b == 0) { temp.GCD = a; temp.x = 1; temp.y = 1; return temp; } else { long long fuck; temp = ex_gcd(b, a%b); fuck = temp.y; temp.y = temp.x - a / b*temp.y; temp.x = fuck; } return temp; } int main() { long long x, y, m, n, L; long long distance, deltav; scanf ( "%lld%lld%lld%lld%lld" , &x, &y, &m, &n, &L); x = x > 0 ? x : L + x; y = y > 0 ? y : L + y; deltav = m > n ? m - n : n - m; distance = m > n ? (y > x ? y - x : L + y - x) : (x > y ? x - y : L - y + x); deltav %= L; //now deltav < L if (deltav == 0) { printf ( "Impossible" ); } else { struct triple key; key = ex_gcd(deltav, L); if (distance%key.GCD == 0) { if (key.x < 0) key.x += ((-key.x) / L + 2)*L; key.x *= (distance / key.GCD); key.x %= L / key.GCD; printf ( "%lld" , key.x); } else { printf ( "Impossible" ); } } } |
注释:
见《算法导论》p545定理31.2