《算法竞赛入门经典(第2版)》P22 例题2-2
猜想:对于任意大于1的自然数n,若n为奇数,则将n变为3n+1,否则变为n的一半。经过若干次这样的变换,一定会使n变为1。
题目:输入n,输出变换的次数,n ≤ \leq ≤ 109。
书中给出的第一种方案(有bug):
#include<stdio.h>
int main()
{
int n,count=0;
sacnf("%d",&n);
while(n>1)
{
if(n%2==1)n=3*n+1;
else n/=2;
count++;
}
printf("%d",count);
return 0;
}
输入987654321
,输出为1
,证明答案错误。
通过调试,发现经过第一次while循环时,n的值为-1332004332.
笔者在此尝试解释一下这个n的成因:
对于int型的变量,为32位,第一位为符号位,0表示整数,1表示负数
所以int型变量的最大值为231-1。
如果超出了int型的最大值,则会发生正向溢出。
例:如果int型变量赋值为231,发生溢出,其二进制表示为1000 0000 0000 0000 0000 0000 0000 0000,在int型变量中表示为-2^31。
如果int型变量赋值为231+1,其二进制表示为1000 0000 0000 0000 0000 0000 0000 0001,在int型变量中表示为-231+1。以此类推
补充:二进制中负数的表示:
用其绝对值的二进制取反加1
例:521的二进制0000 0000 0000 0000 0000 0010 0000 1001
取反为1111 1111 1111 1111 1111 1101 1111 0110
+1后为1111 1111 1111 1111 1111 1101 1111 0111即为-521的二进制表示
所以输入987654321,计算987654321*3=2962962963超过了231,
2962962963-231=815479315 根据上文所述的规律,
该结果应该为-231+815479315=-1332004333
所以n=3*n+1=-1332004332即为我们输出的结果
回到本题,本题中n的上限109只比int的上界稍微小一点,要使用C99中新增的long long即可解决问题,其范围为-263~263-1,唯一的区别是要把输入时的%d改为%lld。但这也是不保险的——在MinGW的gcc中,要把%lld改成%I64d,但VC2008中又得改回%lld,所以设计long long的输入输出,一般使用C++的输入输出流或自定义的输入输出方法。
下面给出的解决方案巧妙的避开了long long格式的输入输出。
#include<stdio.h>
int main()
{
int n2,count=0;
scanf("%d",&n2);//此处使用%d因为10^9未超过int的上界
long long n=n2;
while(n>1)
{
if(n%2==1)n=3*n+1;
else n/=2;
count++;
}
printf("%d",count);
return 0;
}