POJ 1061青蛙的约会题解

网上似乎有不少此题的解法。我这个post和其他人的相比主要时想说下面几点。
1. 给出一个试图不死记硬背公式的思路;
2. 谈谈暴力解为什么看起来这么诱人,却无法通过;
3. 一些细节及对此题的评价(个人观点)

原题请参考poj: http://poj.org/problem?id=1061

我认为ACM/ICPC应该给人思维的快乐,甚至带有幽默感。这是几个世纪以来数学趣题吸引人们的那种美。
人们会废寝忘食地思考一道题目,解开的一刹那会不由自主的说: 啊--啊哈--哈哈

但是这道题目丝毫没有给我这种快感,这是一道被中国式的考试(竞赛)制度八股化的题目。
首先一看此题要求无解时给出Impossible的输出,就知道一定是要运用数论中的线性同余知识了。

真正思维的快乐在一开始的建模部分。很容易写出下面的等式:
(x + a m) mod L = (y + b n) mod L

我一开始认为两只青蛙跳跃的次数是不同的,一个为a, 另外一个为b,要求出最小的a + b。如果
题目真是是这样,那就有趣多了。可惜本题主旨是要靠死记硬背。所以实际上题意是a == b。

这样的话,上面的等式就化简为求最小的a,使得:
(x + a m) mod L = (y + a n) mod L

利用线性同余进一步化成如下形式:
a x == b (mod n)
其中 a = (n - m)
b = (x - y)
n = L

现在的要求就是,首先判断此线性同余方程是否有解,有解的话,给出最小正数解。
这时就必须死记硬背了。题目开始无趣了。查wikipedia得知:
[1] http://en.wikipedia.org/wiki/Linear_congruence_theorem
这个线性方程有解,当且仅当gcd(a, n)能整除b

而求gcd,当然就是递归的hello world,Euclidean算法了。可惜我脑子不好,记不得了,所以自己推导一下。
若求gcd(a, b),假设a < b, 那么一定可以写成:
b = a q + r

其中q是商,r是余数。假设d = gcd(a, b); 那么显然d能整除b和a,所以d也能整除上式的r。由于r 一定小于a
所以,我们只要求 gcd(r, a)。于是问题的规模就递归化简了。再想想边界条件,显然是 a | b。这时r为0.

所以可以轻易写出gcd的Euclidean算法了:

int gcd(int a, int b) {    return a == 0 ? b : gcd(b % a, a);}

于是就可以判定是否有解了。接着考虑有可能出现a或者b为负数的情形,所以需要处理下输入。正好最近读了
<<短码之美>>,索性扬弃地写出了下面的代码:

int gcd(int a, int b) {    return a == 0 ? b : gcd(b % a, a);}

main(a, b, x, y, m, n, L) {    scanf("%d%d%d%d%d", &x, &y, &m, &n, &L);    a = m < n ? n - m : m - n;    b = m < n ? x - y : y - x;    b = (L + b) % L; // avoid negative b    if ( b % gcd(a, L))        printf("Impossible\n");    else        ...???...}

现在的问题是...???...的部分怎么写? 第一个思路当然是暴力解了:

if ( b % gcd(a, L))    printf("Impossible\n");else {    for (x = 0, n = 0; x != b; x += a, x -= (x >= L) ? L : 0, ++n);    printf("%d\n", n);}

Jon Bentley在Porgramming Pearls中告诉我们,取mod是个很慢的操作,所以我就用加法和减法达到了同样的目的。

用样本测试下能通过,然后上POJ,一下子失败了,结果是Wrong Answer,结果发现这个题目的输入数据不只一行,
(虽然题目说是一行),于是把scanf等用循环包起来。

接着还是Wrong Answer.再次读题发现,输入数据很大,是个10位数。
10位数用long能装下,但是一旦做乘法就危险了。这就是网上其他的答案中要么用long long,要么用int64的原因。
可是我的暴力解法中,没有乘法,我用加法减法化简了。所以我可以用long,改成long后上POJ,这次仍然fail了。
原因是超时Exceed Time Limit。

我自己试了试,解同余方程 11 x == 2000000000 (mod 2100000000) 的确很慢,大约用了7,8秒的样子,而题目
要求是1秒内计算出答案。

到了这里,明白这道题目就是拒绝暴力解法的。剩下的唯一思路就是快速解线性同余方程了。到这里彻底看透了考察死记硬背
的无聊用意。再次查看wikipedia,看到了Extended Euclidean algorithm:
[2] http://en.wikipedia.org/wiki/Linear_congruence_theorem#Solving_a_linear_congruence

也就是说,通过Extended Euclidean算法可以得到 a x + b y = gcd(a, b)的线性组合x和y。
这样就可以直接得到特解x0 = x b / d,其中d = gcd(a, b)。其他的解都可以表达为:
x0 + k n / d的形式,其中k为整数。

到这里已经很无趣了,实现Extended Euclidean就可以了。回想起当年看CLRS算法导论的时候,讲解RSA算法时,说过
这个扩展Euclidean。伪代码如下:

function egcd(a, b)
if b == 0
return (a, 1, 0)
(d', x', y') = egcd(b, a mod b)
return (d', y', x' - a / b * y')

翻译成C, 然后上POJ,又失败了。这次是wrong answer,这是意识到Extended Euclidean返回的x可能是负数。例如:
12 x == 20 (mod 28), 不加以处理的话,结果是-10. 于是稍作调整得到最终的程序。

翻译为C就是:

typedef long long BIG;

BIG egcd(BIG a, BIG b, BIG *x, BIG *y) {    BIG d, x1, y1;    if (b == 0) {        d = a;        *x = 1;        *y = 0;    }    else {        d = egcd(b, a % b, &x1, &y1);        *x = y1;        *y = x1 - a / b * y1;    }    return d;}

main() {    BIG a, b, x, y, m, n, L, d;    while(scanf("%lld%lld%lld%lld%lld", &x, &y, &m, &n, &L) != -1) {        a = m < n ? n - m : m - n;        b = m < n ? x - y : y - x;        b = (L + b) % L; // avoid negative b        d = egcd(a, L, &x, &y);        if ( b % d)            printf("Impossible\n");        else {            for(x *= b / d, L /= d; x < 0; x += L); /*hanldle negative answer case. e.g. 12 x == 20 (mod 28)*/            printf("%lld\n", x % L);        }    }}

上POJ,终于AC了。

最后我再次认为这倒题目很无趣,整个题目的设计,尤其是细节,都强迫人走同一条道路。
由于POJ不支持Python, Scheme等(这点ZOJ就好多了),必须使用64位整数,然后就是死记硬背扩展Euclidean算法。
好在这道题目是中文的,免得拿到世界大赛上贻笑大方了。

POJ 1061青蛙的约会题解相关推荐

  1. poj 1061青蛙的约会

    青蛙的约会 两只青蛙在网上相识了,它们聊得很开心,于是觉得很有必要见一面.它们很高兴地发现它们住在同一条纬度线上,于是它们约定各自朝西跳,直到碰面为止.可是它们出发之前忘记了一件很重要的事情,既没有问 ...

  2. POJ 1061 青蛙的约会(扩展欧几里得)

                                                                   青蛙的约会 Time Limit: 1000MS   Memory Lim ...

  3. 数学--数论--POJ 1061青蛙的约会 (扩展欧几里得算法)

    青蛙的约会 两只青蛙在网上相识了,它们聊得很开心,于是觉得很有必要见一面.它们很高兴地发现它们住在同一条纬度线上,于是它们约定各自朝西跳,直到碰面为止.可是它们出发之前忘记了一件很重要的事情,既没有问 ...

  4. POJ 1061 青蛙的约会(扩展GCD求模线性方程)

    题目地址:POJ 1061 扩展GCD好难懂..看了半天,终于把证明什么的都看明白了..推荐一篇博客吧(戳这里),讲的真心不错.. 直接上代码: #include <iostream> # ...

  5. POJ 1061 青蛙的约会 (扩展欧几里得)

    青蛙的约会 Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 97673   Accepted: 18409 Descripti ...

  6. 同余方程 ax≡1(mod b) POJ 1061 青蛙的约会

    题目:求 ax%b=c ax\%b=c最小正整数x解,题目中的 c c=1. 先感谢两位大犇ngncmh和笑巧. 对于一般的问题,我们通常有两种做法: 1)1) Baby Step Giant Ste ...

  7. POJ 1061 青蛙的约会(扩展欧几里德)

    题目链接:http://poj.org/problem?id=1061 Description 两只青蛙在网上相识了,它们聊得很开心,于是觉得很有必要见一面.它们很高兴地发现它们住在同一条纬度线上,于 ...

  8. POJ - 1061 青蛙的约会(扩展欧几里得)

    题目链接:点击查看 题目大意:两只青蛙在一个单向循环数轴上跳动,给出初始位置和每秒跳动的距离以及数轴长度,问是否可以相遇,若能相遇求出最小时间 题目分析:自从第一次接触扩展欧几里得以来已经有半年时间了 ...

  9. poj 1061 青蛙的约会

    一只青蛙1一开始在x位置,另一只青蛙2在y位置.青蛙1每次跳m米,青蛙2每次跳n米,并且都是向右跳的.地球经线长度是L,然后地球是圆的,也就是说,对L取模:问多少次后它们能跳到一起.如果它们永远不能相 ...

最新文章

  1. 从一道面试题分析Thread.interrupt方法
  2. lvs(+keepalived)、haproxy(+heartbeat)、nginx 负载均衡的比较分析
  3. ansible的条件判断、迭代执行、tags
  4. Java JUC工具类--Exchanger
  5. SendInput模拟键盘输入的问题 转
  6. awk命令和grep命令的使用
  7. 强制卸载软件包linux,强制删除rpm包的方法
  8. php mysql sum_thinkphp mysql语句 sum
  9. Kubernetes详解(十四)——Pod对象生命周期
  10. html设置图片高度宽度自适应屏幕,css让图片自适应屏幕大小的方法
  11. Springboot毕设项目公共机房的值班管理系统wyz7b(java+VUE+Mybatis+Maven+Mysql)
  12. 人力资源管理系统HRMS 天下三分 煮酒论英雄
  13. 苹果6s上市时间_6s为什么会在iOS14系统支持名单?
  14. VM的三种网络连接方式
  15. 怎么删除微信的手机充值服务器,微信如何一键清空账单?全部删除的方法
  16. 怎么把pdf文件压缩到最小?四招快速压缩!
  17. 笔记本电脑键盘输入错误如何解决 电脑按键错乱的解决方法步骤
  18. Python常见笔/面试题
  19. 量子计算机研制成功图片,光量子计算机的曙光:科学家成功研制出量子光源
  20. (转载)C++中的头文件

热门文章

  1. Python实现小游戏--2048
  2. 3设置和使用烤面包机
  3. 状态栏 (+强制修改状态栏颜色)/ 导航栏 / 底部导航Tabbar 常用设置
  4. 南邮CTF:密码学 骚年来一发吗
  5. python竖着展示诗_Python把一段字符串用“右起竖排”的古文格式输出
  6. vsto 启用 禁用加载项_新的Outlook VSTO加载项:如何禁用Outlook 2007中的全部答复和转发...
  7. 基于RK3399分析Linux系统下的CPU时钟管理 - 第1篇
  8. 涂料选购小秘诀 环保涂料品牌哪些好
  9. H5 WebSQL每日成语
  10. 狼图腾--草原上狼的作用