题目简述
题意很简单:将给定的数字中所有位上的奇数变为偶数,每次只能增1或减1,最少需要多少步?
思路
其实就是找到离该数最近的各个位均为偶数的数,因为数轴是向两边无限延伸的,所以这个最近意味着在左右两边最近的数中再选一个近的。剩下的就是如何找到这两个最近的数了。
考虑到需要判断每个位上的奇偶,我们选择string来存放给定数字N,这样循环的长度就从N缩减到了string的长度。同时注意到,只有最左奇数才会影响结果(eg:208<—21x—>220,无论个位上的数字是几都不会改变),因此我们可以只需从左向右遍历string,找到第一个(即最左)奇数进行计算即可。
从下面开始,这道题目的坑才真正浮现。
先是过滤不需要处理的情况,我写下了这行代码
if((s[s.length()-1]-'0')%2!=0)//最末位数字是偶数则N为偶数,不需处理
fun(....);
Sample测试用例中2018的运行结果竟然是0,原来是这个if没有考虑含有奇数位的偶数,改掉,这部分测试用例通过,但是提交运行得到WA,看来是整个思路存在漏洞。
起初我想这很简单,遇到奇数将它加1减1就可以分别得到它的左右,然后剩下的部分向左是88…8,向右是00…0,但是事实证明这样“想当然”会导致重要边界值的遗漏!!,直到我提交了很多次代码都WA才老老实实的把一些看似简单的信息写下来:0<—1—>2,2<—3—>4,4<—5—>6,6<—7—>8,8<—9—>20。等等,9的右边是20,如果按刚才的想法得出的是10,又产生了新的奇数!那把9的情况单拿出来讨论,仍然WA。
鉴于上述进位导致的新的奇数,我又多考虑了一个情况:…89…,此时8也会导致进位,但由于它是偶数所以上述代码里并没有将它考虑进去。于是我列出了一些组合:89,889,689…得出一个结论,当8和9相连时,要考虑更前面的进位,因此在9的讨论中又分为9前面是8和9前面不是8。于是得到如下代码:
#include<iostream>
#include<string>
#include<algorithm>
#include<math.h>
using namespace std;
void fun(string s,int i)
{
long long res=0;
if(s.length()==1&&s[0]%2!=0)
res=1;
else
{
for(int j=0;j<s.length();j++)
{
long long d_l,d_r;
long long temp=atoi(s.substr(j).c_str());
d_l=temp-(s[j]-'0'-1)*pow((double)10,double(s.length()-j-1))-atoi(string(s.length()-j-1,'8').c_str());
if(s[j]=='9')
{
if(j>0&&s[j-1]=='8')
{
j--;
while(j>0&&s[j]=='8')j--;
d_r=2*pow((double)10,double(s.length()-j))-atoi(s.substr(j).c_str());
}
else
d_r=2*pow((double)10,double(s.length()-j))-temp;
res=min(d_l,d_r);
break;
}
if((s[j]-'0')%2!=0)
{
d_r=(s[j]-'0'+1)*pow((double)10,double(s.length()-j-1))-temp;
res=min(d_l,d_r);
break;
}
}
}
cout<<"Case #"<<i<<": "<<res<<endl;
}
int main()
{
int T;
cin>>T;
for(int i=0;i<T;i++)
{
string s;
cin>>s;
fun(s,i+1);
}
return 0;
}
果然,通过了Small Dataset,但是Large Dataset仍旧WA。不过这种情况可以推测程序思路应该是正确的,只是没有考虑到大数据集的边界。拿上限10^16一试,发现结果竟然是0。Debug一下,发现问题出在string到long long的转换上,atoi最大也就是Int,因此发生了溢出。将atoi改为strtoll后仍然WA,当我debug 9999 9999 9999 9999 这个边界值时,发现中间结果莫名其妙的出错了,原来是pow()函数返回值为double,直接把这个结果放在一堆long long的数里进行计算,忽略了double的精度丢失。于是显示转换为long long,运行正确。
这道题本身很简单,但是稍微一个细节没注意就会出错,也更加提醒我考虑要周全,找到关键的边界值才能及时发现错误。