2 、神奇数
【 题目描述】东东在一本古籍上看到有一种神奇数,如果能够将一个数的数字分成两组,其中一组数字的和
等于另一组数字的和,我们就将这个数称为神奇数。例如 242 就是一个神奇数,我们能够将这个数的数字分
成两组,分别是{2,2}以及{4},而且这两组数的和都是 4.东东现在需要统计给定区间中有多少个神奇数,即
给定区间[l, r],统计这个区间中有多少个神奇数,请你来帮助他。
输入描述: :
输入包括一行,一行中两个整数 l 和 r(1 ≤ l, r ≤ 10^9, 0 ≤ r - l ≤ 10^6),以空格分割
输出描述: :
输出一个整数,即区间内的神奇数个数
输入示例:
1 50
输出示例:
4
答案及解析:
方法一:(暴力求解)
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
#include<stdio.h>
#include<windows.h>
SYSTEMTIME time1, time2;///////////////计时
//两计时器相减得出程序运行时间
void my_count_and_print_time(SYSTEMTIME& time1, SYSTEMTIME& time2)
{
printf("\n\n程序运行了 %d毫秒 \n", (1000 * time2.wSecond + time2.wMilliseconds) - (1000 * time1.wSecond + time1.wMilliseconds));
}
//将数n的每一位放入数组,将位数存入arr_num
void spr(int n, int arr[],int* arr_num)
{
int elem = -1;
int i = 0,j=0;
while (n != 0)
{
elem = n % 10;
n /= 10;
arr[i++] = elem;
++(*arr_num);
}
}
//判断是否为神奇数
bool is_shenqishu(int arr[],int* arr_num)
{
int sum_bin = 0;
int size = *arr_num;
for (int i = 0; i < size; i++)
sum_bin += arr[i];
if (sum_bin % 2 != 0)
return false;
sum_bin = sum_bin/2;
for (int one = 0; one < *arr_num; one++)
{
if (arr[one] == sum_bin)
return true;
for (int two = one + 1; two < *arr_num; two++)
{
if (arr[one] + arr[two] == sum_bin)
return true;
for (int three = two + 1; three < *arr_num; three++)
{
if (arr[one] + arr[two] + arr[three] == sum_bin)
return true;
for (int four = three + 1; four < *arr_num; four++)
{
if (arr[one] + arr[two] + arr[three] +arr[four]== sum_bin)
return true;
for (int five = four + 1; five < *arr_num; five++)
{
if (arr[one] + arr[two] + arr[three] + arr[four] + arr[five] == sum_bin)
return true;
}
}
}
}
}
return false;
}
void jd_shenqishu()
{
const int maxnum = (int)1e9;
const int distance = (int)1e6;
int left = 0;
int right = 0;
int arr[11];
int result = 0;
int arr_num = 0;
scanf("%d %d", &left, &right);
GetLocalTime(&time1);///////////////计时开始
if (left<1 || right>maxnum || ((right - left) > distance) || left > right)
{
printf("输入不合要求!\n");
return;
}
if (left < 10) left = 10;
for (int n = left; n <= right; n++)
{
memset(arr, 0xffffffff, sizeof(arr));
arr_num = 0;
spr(n,arr,&arr_num);
if (is_shenqishu(arr, &arr_num))
{
//printf("%4d \n", n);
result++;
}
}
printf("\n 区间[ %d — %d ]内共有神奇数 %d 个 \n\n",left,right,result);
}
int main()
{
jd_shenqishu();//区间:1-10^6 用时:151ms 神奇数个数:376413
//jd_shenqishu_2();//区间:1-10^6 用时:488ms 神奇数个数:376413
GetLocalTime(&time2);///////////////计时结束
my_count_and_print_time(time1, time2);
return 0;
}
代码看起来很烦,我简单来说明解决方法的思想是什么:
1.首先,题目中限定了输入的数字的区间和范围,由题目可知要判断是不是神奇数的数字最多不超过10位,我们就搞一个数组,用来存储数字分离出来的每一位。
2.接下来,我们要考虑的问题是,怎么判断是不是一个神奇数,题目中说,如果一个数字分成两组,两组之和相等,那么如果一个数的所有位之和是奇数,那它必然不是神奇数,因为它分成两组,两组之和必然不能相等。
3.然后,我们怎么判断所有位之和为偶数的数字是不是神奇数呢?这个数被分为了两组,那么,我们就只需要判断其中一组的和是不是所有位之和的一半就好了。如果是,那就是神奇数。
举两个例子:
数字:25732(2+3+7+5+2==19,19是奇数,所以2,5,7,3,2必然不能分为和相等的两组,所以不是神奇数)
数字:12452(2+5+4+2+1==14,14是偶数,再判断是否存在一个组合,组合内数字的和为7(14/2==7),很明显2+5==7,于是12452是神奇数)
有了上面的思路,问题的解决方法就很清晰了,再有的绊脚石就是代码中的算法了。
我们需要这些算法:1.将一个数字的每一位分离出来。2.将数组中的数字任意组合为一组然后组内求和。
第1个很简单,先对这个数模10 得到个位,然后再除10,将已经取得的个位去掉,然后之前的十位又变成了个位,就这样循环不停的搞它,直到它变成0,我们就取得了它的每一位。
举个栗子:数字245,245%10等于5,我们就得到了个位的5,赶紧存起来,然后245/10等于24 就把已经得到的5给抛弃了;接下来再24%10等于4,得到了4,24/10等于2抛弃了4;最后2%10 得到2,2/10等于0了,就循环结束,这时候数组里就存的5 4 2。
第2个也不难,这个描述起来有点绕,我们画个图,看一眼就知道怎么回事了。
我解释一下这张图,选了数字12452来判断其是否为神奇数(数组为arr[11],不放有效位的空间我们放置-1),它的组合方式有多少种不难算,就全组合(C 5,1 + C 5,2 + C 5,3 + C 5,4 等于 30 种 )可是呢,我们就能发现,它的第一种和最后一种是殊途同归,虽然第一种是选1个,第四个是选4个。但是,两组并不做分别,所以就有1倍的重复。于是我们就能发现,5位的数,我们只要做到两位的全组合,3位以后的组合方法已经全部囊括再2位以内了。当然在写循环时,将选两位的方式叠在选一位的方式内,将选三位的方式叠在选三位的方式内就很方便。(如果你不知道这句话啥意思,亲自写代码解这道题,你就知道了)
到这里,我们这种暴力求解的方法就赤裸裸的站在我们面前了。
接下来,我们来看第二种方法:
方法二:
#include<stdio.h>
#include<windows.h>
SYSTEMTIME time1, time2;///////////////计时
void my_count_and_print_time(SYSTEMTIME& time1, SYSTEMTIME& time2)
{
printf("\n\n程序运行了 %d毫秒 \n", (1000 * time2.wSecond + time2.wMilliseconds) - (1000 * time1.wSecond + time1.wMilliseconds));
}
void spr(int n, int arr[],int* arr_num)
{
int elem = -1;
int i = 0,j=0;
while (n != 0)
{
elem = n % 10;
n /= 10;
arr[i++] = elem;
++(*arr_num);
}
}
bool is_shenqishu_2(int arr[], int* arr_num)
{
bool judge_arr[42] = { 0 };
int sum_bin = 0;
int size = *arr_num;
for (int i = 0; i < size; i++)
sum_bin += arr[i];
if (sum_bin % 2 != 0)
return false;
sum_bin = sum_bin / 2;
judge_arr[arr[0]] = true;
for (int i = 1; i < size; i++)
{
int v = arr[i];
for (int j = 41; j >= 0; j--)
{
if (judge_arr[j] && (j + v <= 41))
{
judge_arr[j + v] = true;
}
}
}
if (judge_arr[sum_bin] == true)
return true;
return false;
}
void jd_shenqishu_2()
{
const int maxnum = (int)1e9;
const int distance = (int)1e6;
int left = 0;
int right = 0;
int arr[11];
int result = 0;
//printf("%d ", maxnum);
int arr_num = 0;
scanf("%d %d", &left, &right);
printf("\n 区间[ %d — %d ]", left, right);
GetLocalTime(&time1);///////////////计时开始
if (left<1 || right>maxnum || ((right - left) > distance || (right < left)))
{
printf("输入不合要求!\n");
return;
}
if (left < 10) left = 10;
if (right < 10) right = 10;
for (int n = left; n <= right; n++)
{
memset(arr, 0xffffffff, sizeof(arr));
arr_num = 0;
spr(n, arr, &arr_num);
if (is_shenqishu_2(arr, &arr_num))
{
//printf("%4d \n", n);
result++;
}
}
printf("内共有神奇数 %d 个 \n\n",result);
}
int main()
{
GetLocalTime(&time1);///////////////计时开始
//jd_shenqishu();//1-10^6 用时:151ms 376413
jd_shenqishu_2();//1-10^6 用时:488ms 376413
GetLocalTime(&time2);///////////////计时结束
my_count_and_print_time(time1, time2);
return 0;
}
(如果没有搞懂第一种方法,可能看不懂第二种,因为我在说明第二种时一些细节没提,这些细节在第一种方法里说明了)
第二种方法其实大致与第一种方法相同,只是在判断是否为神奇数这里有点区别,我在这里就介绍第二种方法是怎样判断是否为神奇数的。
1.首先和为奇数就不是神奇数了,第一种方法已经说明原因。
2.接下来,就不像第一种方法那样对其分组了,而是直接来算所有 可能的 并且 不重复的 分组的和,再将sum/2与其比较。听起来不知道啥意思是不是,来画图说明。
如果没看明白,你自己搞着画一遍,这个流程就很清晰了。
那么就有个问题:judge_arr要多大呢?我们考虑和最大的数也就是999999999,和最大是81,81/2 就是40.5,我们取42,超过41的值我们就不用考虑。
这时候,就有点感觉好像不太靠谱的样子,那么,为什么这样就能判断是不是一个神奇数呢?原因其实也简单,我们顺序的将数组里的数字放入judge_arr然后将后面的数与前面的相加,其实加出来的结果不都是数组里数字的和吗,而且结合之前第一种的重复的情况,忽略一些重复的计算,所以这样可以判断是否是神奇数。
可以看到,在程序里,我分别对两种解法的运行时间做了计算(测试如下表),第一种方法很暴力很无脑,却很快,第二种方法巧一点,但是因为那个judge_arr[]速度慢很多,好像是想空间换时间,结果弄巧成拙。