您的位置:首页 > 其它

蓝桥杯 花朵数 解题报告

2015-02-25 21:20 537 查看
一个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次方之和正好等于这个数本身。

如果满足条件的数字不只有一个,请从小到大输出所有符合条件的数字,每个数字占一行。因为这个数字很大,请注意解法时间上的可行性。

这次我重新修改一下这篇文章,之前的内容在下面,一开始,我是直接按照大数运算的方法,从10000......到99999........暴力枚举,虽然程序没有出结果,但是我将正确的答案带进去发现计算模块和验证模块是正确的,于是就心满意足了,也没有验证一下计算出结果到底需要多久,今天看了一个教学视频,恍然大悟,回想一下以前的分析算法复杂度,CPU的运算速度为10^9次/秒,而要计算的数超过10^21个,就算纯粹的枚举数字,都组要10^12秒,换算下来接近31709年。。。在我有生之年是看不到结果了。。。所以这次换了一个方法,个人感觉很巧妙,原代码是java版的,还好鄙人学了一个学期的java,研究了一下,下面说说分析过程。

首先,我们任然使用之前的大数系统,也就是通过字符串来表示大数。因为题目要求是说每一位的21次方之和,也就是说,这个和实际上跟数字的顺序并没有关系,举个简单的例子,123和321的每一位的3次方之和并没有区别,然后我们就可以换一个角度来考虑这个问题,如果纯粹的枚举数字,必定会出现大量的重复,为了避免重复,我们直接枚举每一个数字出现的次数,比如0出现3次,1出现2次,2出现4次。。。如果能很好的处理这个排列组合,使之不发生重复,那效率提高了不是一点点。

关键问题是怎样模拟这个排列呢,总不能用10个for循环吧,那种太low了,在循环太多的情况下,我们用什么方法呢?没错,就是递归。

在递归的时候,我们要枚举从0到9这10个数每一个数出现的次数,递归出口当然就是枚举到9时,因为总位数为21位,只要前9个数出现的次数确定了,那第十个数(9)出现的次数也就确定了,就是21-used,当获得了一组数据nn[]之后,我们将这组数据所代表的和sum[]求出来,这里注意,因为枚举的是每一个数字出现的次数,所以我们并不知道那个21位数到底是几,但是,我们可以通过nn[]这组数据,把一个21位的空数组num[]填满,不用考虑顺序,填满之后,我们把sum和num进行排序,如果这两个数组的每一位都相等,那说明这个sum的值就是我们要求的值,输出即可。

这个代码在我i7的本子上运行80+s,还算可以了



#include<iostream>
#include<algorithm>
#include<memory.h>
#include<cstring>
using namespace std;
int square[10][21];
int num[21]={0};
int sum[21]={0};

void carry(int *t)
{
int i;
for(i=19;i>=0;i--)
{
t[i]=t[i]+t[i+1]/10;
t[i+1]=t[i+1]%10;
}
}

void add(int *a,int *b)
{
int i;
for(i=0;i<=20;i++)
{
a[i]=a[i]+b[i];

}
carry(a);
}

void mul(int *a,int b)
{
int i;
for(i=0;i<21;i++)
a[i]=a[i]*b;
carry(a);
}

void showNum(int *n)
{
int i;
for(i=0;i<21;i++)
cout<<n[i];
}

void init()
{
int i,j;
for(i=0;i<10;i++)
{
for(j=0;j<20;j++)
{
square[i][j]=0;
}
square[i][20]=i;
}
for(i=0;i<10;i++)
{
for(j=0;j<20;j++)
{
mul(square[i],i);
}
}
}

int judge()
{
int i;
int temp[21];
for(i=0;i<21;i++)
temp[i]=sum[i];
sort(temp,temp+21);
sort(num,num+21);
for(i=0;i<21;i++)
{
if(num[i]!=temp[i])
return 0;
}
return 1;
}

void getSum(int *nn)
{
int i,j,p;
p=0;
for(i=0;i<10;i++)
{
for(j=0;j<nn[i];j++)
{
add(sum,square[i]);
num[p++]=i;
}
}
if(sum[0]<10&&judge())
{
if(sum[0]>0)
{
showNum(sum);
cout<<endl;
}
}
memset(sum,0,sizeof(sum));
}

void dfs(int *nn,int pos,int used)
{
if(pos==9)
{
nn[9]=21-used;
getSum(nn);
return;
}
for(int i=0;i<=21-used;i++)
{
nn[pos]=i;
dfs(nn,pos+1,used+i);
}
}

int main()
{
int i,j;
int nn[10]={0};
init();
dfs(nn,0,0);
return 0;
}

下面的方法适合在银河那样的超级计算机上运行,土豪专用。。。

没有想到什么好的解题方法,就是暴力枚举,不过枚举这么多数花的时间确实不少,关键是21位数的运算没有什么好用的数据类型,只能按照大数来处理,既然是大数,当然需要要用字符串来表示,这无疑更加降低了运算速度,然后要预处理一下,就是把0-9的21次方都事先算出来,不过这个高精度运算同样要按照大数原则运算,否则位数太长,然后很不情愿的又要编一个大数乘法函数,函数中所有的数都应该按照21位处理,虽然浪费点内存,但代码清晰,最后就是把每一位数的21次方相加,看得到的和是否跟那个数一样,因此还需要一个判断函数,就这样,我写出了程序,但很明显需要优化,因为运算出结果所花的时间太长了,不过我验证了结果,是正确的,希望大牛能想出更加高效的算法。

#include<iostream>
#include<memory.h>
using namespace std;
int square[10][21];
int num[21]={0};
int sum[21]={0};

void carry(int *t)
{
int i;
for(i=19;i>=0;i--)
{
t[i]=t[i]+t[i+1]/10;
t[i+1]=t[i+1]%10;
}
}

void add(int *a,int *b)
{
int i;
for(i=0;i<=20;i++)
{
a[i]=a[i]+b[i];

}
carry(a);
}

void mul(int *a,int b)
{
int i;
for(i=0;i<21;i++)
a[i]=a[i]*b;
carry(a);
}

void showNum(int *n)
{
int i;
for(i=0;i<21;i++)
cout<<n[i];
}

void init()
{
int i,j;
for(i=0;i<10;i++)
{
for(j=0;j<20;j++)
{
square[i][j]=0;
}
square[i][20]=i;
}
for(i=0;i<10;i++)
{
for(j=0;j<20;j++)
{
mul(square[i],i);
}
}
}

void getSum()
{
int i;
for(i=0;i<21;i++)
{
add(sum,square[num[i]]);
}
}

int judge()
{
int i;
for(i=0;i<21;i++)
{
if(sum[i]!=num[i])
return 0;
}
return 1;
}

int main()
{
int i,j;
init();
/*for(i=0;i<10;i++)
{
for(j=0;j<21;j++)
{
cout<<square[i][j];
}
cout<<endl;
}*/

int temp[21]={0};
temp[20]=1;
num[0]=1;
for(i=0;num[0]!=2;i++)
{
add(num,temp);
getSum();
if(judge())
{
showNum(num);
cout<<endl;
}
memset(sum,0,sizeof(sum));
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息