zoukankan      html  css  js  c++  java
  • 算法 之 3n+1问题

    卡拉兹(Callatz)猜想:

      对任何一个自然数n,如果它是偶数,那么把它砍掉一半;如果它是奇数,那么把(3n+1)砍掉一半。这样一直反复砍下去,最后一定在某一步得到n=1。卡拉兹在1950年的世界数学家大会上公布了这个猜想,传说当时耶鲁大学师生齐动员,拼命想证明这个貌似很傻很天真的命题,结果闹得学生们无心学业,一心只证(3n+1),以至于有人说这是一个阴谋,卡拉兹是在蓄意延缓美国数学界教学与科研的进展……

    猜想内容: 对于任意大于1的自然数n,若n为奇数,则将n变为3n+1,否则变为n的一半。经过若干次这样的变换,一定会使n变为1。例如3->10->5->16->8->2->1。
          输入n,输出变换的次数。n≤10^9。
          样例输入:3
          样例输出:7    (有的博客输出为5是考虑的变化次数 这里是把每一次出现的都算作一次)

    #include<stdio.h>
    int main(void)
    {
        int n;
        int count=0;
        scanf("%d", &n);
        while(n > 1)
        {  
            if(n%2 != 0)
                n = (3*n + 1)/2;
            else
                n /= 2;
            count++;
        }
        printf("%d ", count);
        return 0;
    }

    然而,程序正确吗?很不幸,如果输入987654321,答案为1,这显然是错误的。通过调试或者在循环体中用printf语句打印出n的值,看到n的值为负数,导致一次循环后程序退出。从这里可以获悉n的值溢出了,因为整型最大值为2^31-1 = 2147483647,大约为21亿,而由题意n的最大值为10亿(10^9),所以在n的值颇大且为奇数时乘以3是危险的,会导致溢出。

    解决方案如下:

    因为奇数*奇数=奇数,所以经过n=3*n+1的计算后,n的值必然是偶数,并且下次循环必然做运算n/=2,所以这里可以合并这两步,也就是n为奇数的情况下做运算n=floor(1.5*n+0.5),由于double值的误差问题,我们可以用n=floor(1.5*n+1)(floor函数接收double类型的参数,返回不大于给定参数的最大整形数,返回值类型为double),将取整后的double值赋给整形从而丢弃小数点。

    #include<stdio.h>
    #include<math.h>
    int main(void)
    {
        int n;
        int count=0;
        scanf("%d", &n);
        while(n > 1)
        {  
            if(n%2 != 0)
            {
                n = floor(1.5*n + 1);
                count += 2;
            }
            else
            {
                n = n / 2;
                count++;
            }
        }
        printf("%d
    ", count);
        return 0;
    }

     使用 long long版本的亦可以算出正确结果

    #include<stdio.h>
    int main()
    {
        int n2;
        int count=0;
        
        scanf("%d", &n2);
        long long n = n2;
    
        while(n > 1)
        {  
            if(n%2 != 0)
                n = (n * 3 + 1)/2;
            else
                n = n / 2;
            count++;
        }
        printf("%d
    ", count);
        return 0;
    }

    参考资料:《算法竞赛入门经典》——刘汝佳

    经验:

    1、c99支持了for循环()内定义变量

    2、while(scanf("%d%d",&begin,&end)!=EOF);

    最后一些疑惑(望看到的高手能解答一二,吾将不胜感激):

    1、这里第二个程序是我依照作者的提示写的,自认应该正确吧:-),但是还是有一些疑惑,比如我们考虑了第一次n的输入值,但是循环

      中的第二次,第三次...呢?我们如何说明以后循环的值不会发生溢出呢?

      比如开始时n的值为7,那么一次循环后其值为11,而11>7。也就是说二次循环的值大于7。

    2、再者,我们如何知道经过若干次的变换后一定会得到1呢,也就是说我们如何知道函数收敛呢?嗯...这貌似是一个数学问题啊。

    关于这个问题的答案我是在数学科普神犇顾森的博客中找到的,其博客地址在这里Matrix67(顺便推荐,很好的博客呢),原文见这里千万别学数学:最折磨人的数学未解之谜(一),下面把与该问题相关的文字摘录在这里:

    数学之美不但体现在漂亮的结论和精妙的证明上,那些尚未解决的数学问题也有让人神魂颠倒的魅力。和 Goldbach 猜想、 Riemann 假设不同,有些悬而未解的问题趣味性很强,“数学性”非常弱,乍看上去并没有触及深刻的数学理论
    ,似乎是一道可以被瞬间秒杀的数学趣题,让数学爱好者们“不找到一个巧解就不爽”;但令人称奇的是,它们的困难程度却不亚于那些著名的数学猜想,这或许比各个领域中艰深的数学难题更折磨人吧。 作为一本数学趣题集, Mathematical Puzzles 一书中竟把仍未解决的数学趣题单独列为一章,可见这些问题有多么令人着迷。我从这一章里挑选了一些问题,在这里和大家分享一下。这本书是
    04 年出版的,
    书里提到的一些“最新进展”其实已经不是最新的了;不过我也没有仔细考察每个问题当前的进展,因此本文的信息并不保证是 100% 准确的,在此向读者们表示歉意。 3x + 1 问题
      从任意一个正整数开始,重复对其进行下面的操作:如果这个数是偶数,把它除以
    2 ;如果这个数是奇数,则把它扩大到原来的 3 倍后再加 1 。序列是否最终总会变成 4, 2, 1, 4, 2, 1, … 的循环?这个问题可以说是一个
    “坑”——乍看之下,问题非常简单,突破口很多,于是数学家们纷纷往里面跳;殊不知进去容易出去难,不少数学家到死都没把这个问题搞出来。已经中招的数学家不计其数,这可以从 3x
    + 1 问题的各种别名看出来: 3x + 1 问题又叫
    Collatz 猜想、 Syracuse 问题、 Kakutani 问题、 Hasse 算法、 Ulam 问题等等。后来,由于命名争议太大,干脆让谁都不沾光,直接叫做 3x + 1 问题算了。 3x + 1 问题不是一般的困难。这里举一个例子来说明数列收敛有多么没规律。从 26 开始算起, 10 步就掉入了“421 陷阱”:26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1, 4, 2, 1, … 但是,从 27 开始算起,数字会一路飙升到几千多,你很可能会一度认为它脱离了“421 陷阱”;但是,经过上百步运算后,它还是跌了回来: 27, 82, 41, 124, 62, 31, 94, 47, 142, 71, 214, 107, 322, 161, 484, 242, 121, 364, 182, 91, 274, 137, 412, 206, 103, 310, 155, 466, 233, 700, 350, 175, 526, 263, 790, 395, 1186,
    593, 1780, 890, 445, 1336, 668, 334, 167, 502, 251, 754, 377, 1132, 566, 283, 850, 425, 1276, 638, 319, 958, 479, 1438, 719, 2158, 1079, 3238, 1619, 4858, 2429, 7288, 3644, 1822,
    911, 2734, 1367, 4102, 2051, 6154, 3077, 9232, 4616, 2308, 1154, 577, 1732, 866, 433, 1300, 650, 325, 976, 488, 244, 122, 61, 184, 92, 46, 23, 70, 35, 106, 53, 160, 80, 40,
    20, 10, 5, 16, 8, 4, 2, 1, 4, 2, 1, …

    额外,再说一点:如上所述,既然数列的收敛如此没有规律,那么上面我们的程序可能是错误的(包括优化过后的那个)。举一个例子,我们用 long long int 做一个测试:

    #include<math.h>
    int main(void)
    {
        long long int n;
        int count=0; // 统计计算次数
        
        scanf("%I64d", &n); // long long int在windows下一定要用%I64d读入数据,否则会出问题(比如数据截断什么的)
        long long max = 0;  // 记录计算过程中出现的最大值
        
        while(n > 1)
        {
            if(n%2 != 0)
            {
                //n = floor(1.5*n + 1);
                  n = (n * 3 + 1)/2;
            }
            else
            {
                n = n / 2;
            }
            count++;
            printf("%I64d
    ", n); // 输出也使用%I64d
            
            if(n > max)
                max = n;
        }
    
        printf("%d
    %I64d
    ", count, max);
    
        return 0;
    }

    我们输入704511,最后那个printf函数的输出结果:242,56991483520。可见其中间值有5百亿这么大,而用 n = floor(1.5*n + 1) 语句代替 n = n * 3 + 1;输出的中间值为28495741760,也有2百亿那么大,int类型存不下这么大的数据,所以之前的两个程序在测试强度不够的情况下表面上看是正确,实则是错误的。
    最后,写的这里,应该可以告一段落了。

    转自 :http://www.cnblogs.com/xpjiang/p/4129340.html

  • 相关阅读:
    POJ 3261 Milk Patterns (求可重叠的k次最长重复子串)
    UVaLive 5031 Graph and Queries (Treap)
    Uva 11996 Jewel Magic (Splay)
    HYSBZ
    POJ 3580 SuperMemo (Splay 区间更新、翻转、循环右移,插入,删除,查询)
    HDU 1890 Robotic Sort (Splay 区间翻转)
    【转】ACM中java的使用
    HDU 4267 A Simple Problem with Integers (树状数组)
    POJ 1195 Mobile phones (二维树状数组)
    HDU 4417 Super Mario (树状数组/线段树)
  • 原文地址:https://www.cnblogs.com/lzhh/p/acm_n1.html
Copyright © 2011-2022 走看看