【幸运数】求解过程

来源:互联网 发布:淘宝服务市场怎么投诉 编辑:程序博客网 时间:2024/06/08 19:49

原题目:
小明同学学习了不同的进制之后,拿起了一些数字做起了游戏。小明同学知道,在日常生活中我们最常用的是十进制数,而在计算机中,二进制数也很常用。现在对于一个数字x,小明同学定义出了两个函数f(x)和g(x)。 f(x)表示把x这个数用十进制写出后各个数位上的数字之和。如f(123)=1+2+3=6。 g(x)表示把x这个数用二进制写出后各个数位上的数字之和。如123的二进制表示为1111011,那么,g(123)=1+1+1+1+0+1+1=6。 小明同学发现对于一些正整数x满足f(x)=g(x),他把这种数称为幸运数,现在他想知道,小于等于n的幸运数有多少个?
输入描述:每组数据输入一个数n(n<=100000)
输出描述:每组数据输出一行,小于等于n的幸运数个数。
当输入21时,输出3,因为在[1,21]的区间内,只有1,20,21这三个数字的十进制和和二进制和相等。
【解题思路】
如果按照题目描述编写两个函数,对给出区间内所有的数字进行二进制和十进制和分别求解,然后在做比较,当给出的数字很大时,在题目规定时间内完成是不可能的,常规思路题目给出不可用,那么只能从十进制和二进制两种不同进制下,各数位上数字之和寻找规律,通过某种规律将100000以内的数位和保留建表,通过查表对比的方式,确定其小于等于n的幸运数个数。由于预设最大值为100000,对此开辟了大小为100001的数组对其数位和进行存储。
以二进制为例,其前16个数字的数位和如下图所示:
前16个数字的数位和
可以发现,每次二进制向前进一位,其后面所有数位又一次重复进位前的所有数字的过程,例如,当N=2时,二进制为10,保持第一个进制位不变,则后面只有一个进制位在改变,改变总数是两个,即10,11。在2之前,有0和1两个数字,则在10改变至11的过程中(从2变化为3),至少在0和1的基础上加1即可。同理,当N=4时,其二进制为100,从100变化到111(即4到7)的过程中一共有4个,去其第一个进制位,后面两个进制位的变化过程为00,01,10,11,即为0到3的变化过程。以此类推,可以将所求范围内的所有数字的二进制数位和求出,其代码如下(注意边界问题)

    vector<int>  datatwo(N, 0);    datatwo[1] = 1;    int index = 2;    while (true)    {        for (int i = 0; i < index; i++)        {            if (index + i < N)                datatwo[index + i] = datatwo[i] + 1;            else            {                index = N;                break;            }        }        if (index >= N)            break;        else            index *= 2;    }

对于十进制上的进制位之和的求法,与二进制类似,只不过在首位进制位上,由于二进制下要么是0,要么是1,所以对于首位后仅仅需要重复前面一次即可完成。但对于十进制则不同。十进制下数位和求解代码如下所示:

    vector<int>  dataten(N, 0);    for (int i = 0; i < 10; i++)        dataten[i] = i;    index = 10;    int cnt = 10;    while (index < N - 2)    {        for (int j = 0; j < 9; j++)            for (int k = 0; k < cnt; k++)                dataten[index + cnt * j + k] = dataten[k] + 1 + j;        cnt *= 10;        index = cnt;    }    dataten[index] = 1;

当数字是个位数时,则其数位和为其本身,保留写进表中(注:从0开始到9,一共10个数字)。当数位为两位数时,对于每个十位上的数字,其对应个位上数字上将变化10次。例如:当十位上数字为1时,其个位上数字从0变化到9,其数位和分别为数字0–9数位和加1的结果。当十位上数字为2时,其个位上数字从0变化到9,其数位和分别为数字0–9数位和加2的结果,以此类推,在10到99的变化过程中,一共需要循环个位数9次,每次按照十位上数字的不同,累加不同的值。当数字是三位数时,规律如两位数相同。通过对数字区间划分,保留百位上数字不变时,循环中的不再仅仅是10个个位数,而是小于最小三位数(即100)100个数字,即100-199,200-299等,直到900-999。边界上,预设值为100001,当index为99999时,退出循环,并对dataten[100000]赋值为1。
验证:每次对于输入的数字,只需要判断从1到该数字上,这一段区间上,两个数组对应位置上的数位和是否相同即可,并累加其总数和。

    int sum = 0;        for (int i = 1; i <= n; i++)            if (dataten[i] == datatwo[i])            {                cout << i << endl;                sum++;            }