题目链接:P5727 【深基5.例3】冰雹猜想 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目描述:给出一个正整数 n(n≤100),然后对这个数字一直进行下面的操作:如果这个数字是奇数,那么将其乘 3 再加 1,否则除以 2。经过若干次循环后,最终都会回到 1。经过验证很大的数字(7×10^11)都可以按照这样的方式比变成 1,所以被称为“冰雹猜想”。例如当 n 是 20,变化的过程是 [20, 10, 5, 16, 8, 4, 2, 1]。
输入格式
无
输出格式
无
输入输出样例
输入
20
输出
1 2 4 8 16 5 10 20
解题思路及过程
这题主要考察数组的存放及输出,涉及到一些基础循环判断的问题,在洛谷看到这题感觉不难,就当练练手,读了读题目立马就有了思路,于是就赶紧去敲了代码。
第一次写的代码:
#include<iostream>
using namespace std;
int main()
{
int n,a[100],c=0,i;
cin>>n;
a[c++]=n;
while(1)
{
if(n%2!=0)
{
n=n*3+1;
a[c++]=n;
}
else
{
n=n/2;
a[c++]=n;
}
if(n==1)
break;
}
for(i=c-1;i>=0;i--)
cout<<a[i]<<' ';
cout<<endl;
return 0;
}
第一次在编译器运行时发现数组少放了输入的数n。所以更改多加了这条语句(即数组第一个位置a[0]存放n):
a[c++]=n;
然后去OJ平台提交了代码:
第一次提交 60 分,两个测试点出现了错误。果然,读完题去敲代码立马提交大概率不能完全通过,会有一些测试点通过不了,一定会忽视一些问题。所以要多读几遍题目,仔细审题,找到题目当中的一些细节(例如数据范围、输出格式等)。
于是我就看了几遍题目,想了想。然后我在想是不是数组开的太小了,所以把数组改成a[100000];然后又去提交了一下:
第二次提交 80 分,果然是因为数组开小了。现在只有一个测试点出现了错误。然后看了看我的代码,我想想是不是可能是 while(1) 的问题,然后我把循环改成 do...while 后,代码如下:
#include<iostream>
using namespace std;
int main()
{
int n,a[100000],c=0,i;
cin>>n;
a[c++]=n;
do
{
if(n%2!=0)
{
n=n*3+1;
a[c++]=n;
}
else
{
n=n/2;
a[c++]=n;
}
}while(n!=1);
for(i=c-1;i>=0;i--)
cout<<a[i]<<' ';
cout<<endl;
return 0;
}
然后又去OJ提交了一下,测试结果如下:
第三个测试点仍然无法通过。
继续更换循环类型,这次用循环用 while 试试,条件判断为 n!=1。while 部分语句如下:
while(n!=1)
{
if(n%2!=0)
{
n=n*3+1;
a[c++]=n;
}
else
{
n=n/2;
a[c++]=n;
}
}
然后去提交代码,终于AC了!!!
这引发了我的思考:为什么用 while(n!=1) 可以,用 while(1) 或 do...while 语句就不行呢?
一开始我在想是不是 while(1) 可能是循环无法终止导致运行超时,然后就不断更改我的循环类型,但我的循环体里面有判断条件可以终止循环,所以就不可能是运行超时的问题。然后我发现while(1) 循环和 do...while 循环有一个共同点,那就是都是先执行循环体,再判断条件。这样的话,当我输入的n为1,那么先执行循环体,那也就是1→4→2→1,最后运行输出1 2 4 1。而当我用 while(n!=1) 时,先判断条件,再执行循环体。即当我输入n为1时,先判断条件,发现 n==1,不满足循环的条件 n!=1,因此不执行循环体,最后输出的结果为1。
然后我下载了测试点,发现确实是当输入为1时,输出为1,这说明我的想法正确的。再次回顾这道题目,可以发现这题应该更加严谨点,可以修改一下n的范围,例如将n的取值范围改为 (1<n<=100),或说明当n为1时不执行操作直接输出1,这样就可以避免歧义。不过这都不是重点,重要的是从错误中汲取经验与教训,下次再遇到类似问题时,可以知道从哪些方面去解决问题。
下面附上我的代码:
AC code 1:
使用 while(n!=1) 先判断条件,再执行循环体。
#include<iostream>
using namespace std;
int main()
{
int n,a[100000],c=0,i;
cin>>n;
a[c++]=n;
while(n!=1)
{
if(n%2!=0)
{
n=n*3+1;
a[c++]=n;
}
else
{
n=n/2;
a[c++]=n;
}
}
for(i=c-1;i>=0;i--)
cout<<a[i]<<' ';
cout<<endl;
return 0;
}
AC code 2:
或者可以修改一下第一次提交的代码,把 while(1) 循环体中的条件判断移到循环体的最前面,这样也可以实现先判断条件再执行循环体。
#include<iostream>
using namespace std;
int main()
{
int n,a[100000],c=0,i;
cin>>n;
a[c++]=n;
while(1)
{
if(n==1)
break;
if(n%2!=0)
{
n=n*3+1;
a[c++]=n;
}
else
{
n=n/2;
a[c++]=n;
}
}
for(i=c-1;i>=0;i--)
cout<<a[i]<<' ';
cout<<endl;
return 0;
}
AC code 3:
再者使用 do...while 语句也可以,这里需要在循环体开头加上判断终止条件语句。
#include<iostream>
using namespace std;
int main()
{
int n,a[100000],c=0,i;
cin>>n;
a[c++]=n;
do
{
if(n==1)
break;
if(n%2!=0)
{
n=n*3+1;
a[c++]=n;
}
else
{
n=n/2;
a[c++]=n;
}
}while(n!=1);
for(i=c-1;i>=0;i--)
cout<<a[i]<<' ';
cout<<endl;
return 0;
}