一个N位的十进制正整数,如果它的每个位上的数字的N次方的和等于这个数本身,则称其为花朵数。
例如:
当N=3时,153就满足条件,因为 1^3 + 5^3 + 3^3 = 153,这样的数字也被称为水仙花数(其中,“^”表示乘方,5^3表示5的3次方,也就是立方)。
当N=4时,1634满足条件,因为 1^4 + 6^4 + 3^4 + 4^4 = 1634。
当N=5时,92727满足条件。
实际上,对N的每个取值,可能有多个数字满足条件。
程序的任务是:求N=21时,所有满足条件的花朵数。注意:这个整数有21位,它的各个位数字的21次方之和正好等于这个数本身。
如果满足条件的数字不只有一个,请从小到大输出所有符合条件的数字,每个数字占一行。因为这个数字很大,请注意解法时间上的可行性。要求程序在3分钟内运行完毕。
分析:
由于double的有效位数只有16位,所以不能用来求21位花朵数,因此首先需要可计算21位数字加,乘法的大数加法和大数乘法函数,这里使用字符串实现。程序要求3分钟内运行完毕,因此尽量避免使用malloc函数,因为其很费时间。
思路1:遍历21位数,找处符合条件数字。[优点:思路简单,容易实现,不易出错;缺点:效率低(在配置为CPU:i5-2430M,操作系统:Windows 7 旗舰版 的机器上遍历所有9位数字耗时3秒,遍历所有10位数字耗时35秒…依此累推,遍历所有21位数字需35*(10的11次方)秒!!!)。]
思路2:尽可能多的排除一些数字,使用递归找出乘方和是21位数的所有21位数,并进一 步判断其是否满足要求。[优点:效率高;缺点:不易实现。]
解:
采用思路2程序如下:
#define __SPEED_PROGRAM
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <malloc.h>
#include <stdlib.h>
#include <math.h>
#define NUMW 21 //数字位数
#define BOOL bool
#define TRUE true
#define FALSE false
typedef char INT8;
typedef int INT32;
typedef double DOUBLE;
typedef void VOID;
INT32 count = 0;
INT8 nPow[10][NUMW+1]; //存放0~9的NUMW次方(ASCII)
INT8 strMin[NUMW+1]; //最小值
INT8 strMax[NUMW+2]; //最大值
clock_t start_addStr, finish_addStr;
DOUBLE duration_addStr = 0;
/***********************************************************************************
* 函数名: addStr *
* 参数: *
* str1: 被加数(ASCII),高位在前,低位在后,'\0'结尾。 *
* str2: 加数(ASCII),高位在前,低位在后,'\0'结尾。 *
* strRes: (out)结果(ASCII),高位在前,低位在后,'\0'结尾。 *
* len: strRes缓冲区长度。 *
* 返回值: TRUE表示成功,FALSE表示失败。 *
* 说明: *
* str1, str2可为同1指针,strRes空间足够即可。 *
***********************************************************************************/
BOOL addStr(INT8 *str1, INT8 *str2, INT8 *strRes, INT32 len)
{
INT32 len1, len2, i, n;
INT8 *pTem;
#ifdef __SPEED_PROGRAM
INT8 pRes[NUMW+2]; //存储空间分配到栈上,节约时间
#else
INT8 *pRes; //动态分配(堆上)
#endif
start_addStr = clock();
if(NULL==str1 || NULL==str2 || NULL==strRes)
{
strRes[0] = '\0';
return FALSE;
}
len1 = strlen(str1);
len2 = strlen(str2);
//使str1指向较长的数
if(len1 < len2)
{
pTem = str1;
str1 = str2;
str2 = pTem;
n = len1;
len1 = len2;
len2 = n;
}
//1:数1或数2为空
if(0 == len1)
{
strcpy(strRes, str2);
return TRUE;
}
if(0 == len2)
{
strcpy(strRes, str1);
return TRUE;
}
#ifdef __SPEED_PROGRAM
n = NUMW + 2;
#else
n = len1 + 2;
pRes = (INT8 *)malloc(n);
#endif
for(i=0; i<n; i++)
pRes[i] = 0;
//计算结果
i = 0;
len1--;
len2--;
while(len2>=0) //2:计算<=len2部分结果
{
n = str1[len1--]-'0' + str2[len2--]-'0';
pRes[i] += n ;
pRes[i+1] += pRes[i]/10;
pRes[i] = pRes[i]%10;
pRes[i++] += '0';
}
for(; len1>=0; ) //3:计算len1>len2部分结果
{
pRes[i] += str1[len1--] - '0';
pRes[i+1] += pRes[i]/10;
pRes[i] = pRes[i]%10;
pRes[i++] += '0';
}
if(pRes[i] > 0) //4:最高位进位?
{
pRes[i++] += '0';
}
if(len < i+1)
return FALSE;
//反转复制
len1 = i;
for(i=0; i<len1; i++)
strRes[i] = pRes[len1-i-1];
strRes[i] = '\0';
#ifndef __SPEED_PROGRAM
free(pRes);
#endif
finish_addStr = clock();
duration_addStr += (DOUBLE)(finish_addStr-start_addStr) / CLOCKS_PER_SEC;
return TRUE;
}
/***********************************************************************************
* 函数名: mulStr *
* 参数: *
* str1: 被乘数(ASCII),高位在前,低位在后,'\0'结尾。 *
* str2: 乘数(ASCII),高位在前,低位在后,'\0'结尾。 *
* strRes: (out)结果(ASCII),高位在前,低位在后,'\0'结尾。 *
* len: strRes缓冲区长度。 *
* 返回值: TRUE表示成功,FALSE表示失败。 *
* 说明: *
* str1, str2可为同1指针,strRes空间足够即可。 *
***********************************************************************************/
BOOL mulStr(INT8 *str1, INT8 *str2, INT8 *strRes, INT32 len)
{
INT32 len1, len2, i, j, n;
INT8 *pRes, *pTem;
pRes = (INT8 *)malloc(len);
memset(pRes, 0, len);
len1 = strlen(str1);
len2 = strlen(str2);
//使str1指向较长的数
if(len1 < len2)
{
pTem = str1;
str1 = str2;
str2 = pTem;
n = len1;
len1 = len2;
len2 = n;
}
for(i=0; i<len2; i++)
str2[i] -= '0';
if(str1 != str2)
for(i=0; i<len1; i++)
str1[i] -= '0';
//计算
for(i=0,j=0; i<len2; i++) //i=0,j=0防止一个串为空导致的错误
{
for(; j<len1; j++)
{
n = str2[len2-i-1]*str1[len1-j-1];
pRes[i+j] += n;
pRes[i+j+1] += pRes[i+j]/10;
pRes[i+j] = pRes[i+j]%10;
}
}
n = i+j;
while(0 == pRes[n] && n>0) //去除高位0
n--;
if(len < n+2)
return FALSE;
//反转复制
for(i=0; i<=n; i++)
strRes[i] = pRes[n-i] + '0';
strRes[i] = '\0';
//恢复str1,str2
for(i=0; i<len2; i++)
str2[i] += '0';
if(str1 != str2)
for(i=0; i<len1; i++)
str1[i] += '0';
free(pRes);
return TRUE;
}
/***********************************************************************************
* 函数名: powStr *
* 参数: *
* str: 乘方数(ASCII),高位在前,低位在后,'\0'结尾。 *
* n: 乘方值。 *
* strRes: (out)结果(ASCII),高位在前,低位在后,'\0'结尾。 *
* len: strRes缓冲区长度。 *
* 返回值: TRUE表示成功,FALSE表示失败。 *
* 说明: *
* strRes为空间足够即可。 *
***********************************************************************************/
BOOL powStr(INT8 *str, INT32 n, INT8 *strRes, INT32 len)
{
INT32 i;
INT8 *pRes;
pRes = (INT8 *)malloc(len);
memset(strRes, '\0', len);
if(0 == n)
{
strRes[0] = '1';
return TRUE;
}
strcpy(strRes, str);
for(i=1; i<n; i++)
{
if(FALSE == mulStr(strRes, str, pRes, len))
return FALSE;
strcpy(strRes, pRes);
}
return TRUE;
}
INT32 nstrcmp(INT8 *str1, INT8 *str2)
{
INT32 len1 = strlen(str1);
INT32 len2 = strlen(str2);
if(len1 > len2)
return 1;
else if(len1 < len2)
return -1;
else
return strcmp(str1, str2);
}
/***********************************************************************************
* 函数名: f *
* 参数: *
* n: 起始数字(0~9,递减的最大值)。 *
* k: 当前计算的数字位数(0~20)。 *
* a: 保存当前计算的21位数字。 *
* res: 21位数字的乘方和。 *
* 返回值: 无。 *
* 说明: *
* 排除尽可能多的数字,验证乘方和为21位数的数字。 *
***********************************************************************************/
void f(INT32 n, INT32 k, INT8 *a, INT8 *res)
{
INT32 i,j;
DOUBLE temR =0;
// INT8 *p, *pTem;
// p = (char*)malloc(NUMW+2);
// pTem = (char*)malloc(NUMW+2);
// memset(p, 0, NUMW+2);
// memset(pTem, 0, NUMW+2);
INT8 p[NUMW+2], pTem[NUMW+2];
if(k==NUMW) //已找到第21位数字?
return;
//将当前位循环赋值为小于n的数字
for(i=n; i>=0; i--)
{
strcpy(pTem, res);
addStr(res, nPow[i], p, NUMW+2); //加入该位的乘方和
if(nstrcmp(p, strMax) < 0) //乘方和没超过21位数?
{
//当前数字初步符合条件
a[k] = i + '0';
strcpy(res, p);
if(k==NUMW-1 && nstrcmp(res, strMin)>0) //找到第21位数字且该21位数乘方和为21位数字?
{
count++;
//计算乘方和(res[j])中每位数字的乘方和
memset(p, '\0', NUMW+2);
for(j=0; j<NUMW; j++)
{
addStr(p, nPow[res[j]-'0'], p, NUMW+2);
}
if(nstrcmp(p, res) == 0) //res[j]中每位数字的乘方和等于其本身?
{
//该数满足条件,输出
printf("%s\n", res);
}
}
//递归计算下一位数字
f(i, k+1, a, res);
}
strcpy(res, pTem);
}
// free(p);
// free(pTem);
}
INT32 main(INT32 argc, INT32 *argv[])
{
INT8 str1[NUMW+1], str2[NUMW+1], strRes[2*NUMW+2];
INT32 i, n, n1, n2, nRes;
clock_t start, finish;
DOUBLE duration;
INT8 c2[2];
INT8 res[2*NUMW+2];
INT8 a[NUMW+1];
memset(a, '\0', NUMW+1);
str1[NUMW] = '\0';
str2[NUMW] = '\0';
memset(strRes, '\0', 2*NUMW+2);
memset(res,'\0', 2*NUMW+2);
srand((unsigned)time(NULL));
/*
//大数加法测试
for(i=0; i<1000000; i++)
{
n1 = rand();
n2 = rand();
itoa(n1, str1, 10);
itoa(n2, str2, 10);
// addStr(str1, str2, strRes);
addStr(str1, str2, str1);
// nRes = atoi(strRes);
nRes = atoi(str1);
if(n1+n2 != nRes)
{
// printf("%d + %d != %d\n", n1, n2, strRes);
printf("%d + %d != %d\n", n1, n2, str1);
}
}
*/
/*
//大数乘法测试
for(i=0; i<1000000; i++)
{
n1 = rand();
n2 = rand();
// n1 = 6473;
// n2 = 828;
itoa(n1, str1, 10);
itoa(n2, str2, 10);
memset(strRes, 0, 2*NUMW+2);
mulStr(str1, str2, strRes, 2*NUMW+2);
// mulStr(str1, str1, strRes, 2*NUMW+2);
nRes = atoi(strRes);
if(n1*n2 != nRes)
{
printf("%d * %d != %d\n", n1, n2, strRes);
}
// if(n1*n1 != nRes)
// {
// printf("%d * %d != %d\n", n1, n1, strRes);
// }
}
*/
/*
//大数乘方测试
for(i=0; i<1; i++)
{
n1 = rand()%10;
n2 = rand()%10;
// n1 = 9;
// n2 = 21;
itoa(n1, str1, 10);
memset(strRes, 0, 2*NUMW+2);
powStr(str1, n2, strRes);
//容易超范围
nRes = atoi(strRes);
if(pow(n1, n2) != nRes)
{
printf("%d ^ %d != %d\n", n1, n2, strRes);
}
}
*/
n = NUMW;
c2[1] = '\0';
for(i=0; i<10; i++)
{
c2[0] = i + '0';
powStr(c2, NUMW, nPow[i], NUMW+1);
printf("%d^%d = %s\n", i, n, nPow[i]);
}
strMin[0] = '1';
strMax[0] = '1';
for(i=1; i<NUMW; i++)
{
strMin[i] = '0';
strMax[i] = '0';
}
strMax[i] = '0';
strMin[i] = '\0';
strMax[i+1] = '\0';
res[0] = '0';
res[1] = '\0';
start = clock();
printf("\nResult:\n");
f(9, 0, a, res);
finish = clock();
duration = (DOUBLE)(finish-start) / CLOCKS_PER_SEC;
printf("\n*****************\nf use %0.3f sec!\n", duration);
printf("*****************\naddrStr use %0.3f sec!\n", duration_addStr);
return 0;
}
程序运行结果图:(CPU:i5-2430M,操作系统:Windows 7 旗舰版):
可以看出程序总运行时间为111.243秒,符合题目要求。
F:\>"F:\蓝桥杯 程序设计_4.exe"
0^21 = 0
1^21 = 1
2^21 = 2097152
3^21 = 10460353203
4^21 = 4398046511104
5^21 = 476837158203125
6^21 = 21936950640377856
7^21 = 558545864083284007
8^21 = 9223372036854775808
9^21 = 109418989131512359209
Result:
449177399146038697307
128468643043731391252
*****************
f use 111.243 sec!
*****************
addrStr use 93.866 sec!
F:\>