基于C++实现水仙花数
实际上,可以穷举 0~9 这 10 个数字出现的次数(每个数字都可能出现 0~5 次),当所有数字出现次数之和等于 5 时,说明这时数字的组合有可能为 5 位花朵数,进而求出每个数字的 5 次方分别乘以其出现的次数的和值 sum,再判断 sum 内各个数字出现的次数是否与穷举各个数字时每个数字出现的次数分别相同,若相同,则 sum 就是一个 5 位花朵数。借用这个概念,在程序设计实践中,我们设计了
1、水仙花数的连营
1.1、水仙花数
在学习程序设计课程时,大多数读者一定采用循环结构编写过求解水仙花数的程序。
【实例 1-1】水仙花数
一个三位整数(100~999),若各位数的立方和等于该数自身,则称其为“水仙花数”(如:153=13+53+33),找出所有的这种数。
编程思路
对三位数 n(n 为 100~999 之间的整数)进行穷举。对每个枚举的 n,分解出其百位 a(a=n/100)、十位 b(b=n%100/10)和个位 c( c=n%10),若满足 aaa+bbb+ccc== n,则 n 是水仙花数。
源程序及运行结果
# include <iostream>
using namespace std;
int main()
{
int n, a, b, c; //n、a、b和c分别为三位数自身及其百位、十位和个位
for(n=100 ;n<=999;n++)
{
a=n/100;
b=n%100/10;
c=n%10;
if(a*a*a+b*b*b+c*c*c== n)
cout<<n<<" ";
}
cout<<endl;
return 0;
}
编译并执行以上程序,得到如下所示的结果。
153 370 371 407
Press any key to continue
1.2、 水仙花数的初步连营
在“三国杀”游戏中,武将陆逊有一个技能是“连营”,其技能描述是:每当你失去最后一张手牌时,可立即摸一张牌。借用这个概念,在程序设计实践中,我们设计了一个程序后,可以在这个程序的基础上,再进行扩展,提出相类似的另一个问题,再进行设计。即解决一个问题后,再解决一个问题,类比解释就是失去一张手牌后,再摸一张牌。在前面水仙花数问题的基础上,我们通过连营,可以提出下面几个问题。
【实例 1-2】4 位花朵数
一个四位整数(1000~9999),若其各位数的 4 次方之和等于该数自身,则称其为 4 位花朵数(如:1634=14+64+34+44),找出所有的这种数。
编程思路
编程的思路完全类同于水仙花数。即对四位数 n(n 为 1000~9999 之间的整数)进行穷举。对每个枚举的 n,分解出其千位 a(a=n/1000)、百位 b(b=n%1000/100)、十位 c(b=n%100/10)和个位 d(d=n%10),若满足 aaaa+bbbb+cccc+dddd== n,则 n 是 4 位花朵数。
源程序及运行结果
#include <iostream>
using namespace std;
int main()
{
int n, a, b, c,d; //n、a、b、c和d分别为四位数自身及其千位、百位、十位和个位
for(n=1000 ;n<=9999;n++)
{
a=n/1000;
b=n%1000/100;
c=n%100/10;
d=n%10;
if(a*a*a*a+b*b*b*b+c*c*c*c+d*d*d*d==n)
cout<<n<<" ";
}
cout<<endl;
return 0;
}
编译并执行以上程序,得到如下所示的结果。
1634 8208 9474
Press any key to continue
【实例 1-3】5 位花朵数
一个 5 位整数(10000~99999),若其各位数的 5 次方之和等于该数自身,则称其为 5 位花朵数(如:54748=55+45+75+45+85),找出所有的这种数。
编程思路
仍然可以采用完全类同于水仙花数的求解方法来求出所有的 5 位花朵数。即对 5 位数 n(n 为 10000~99999 之间的整数)进行穷举。对每个枚举的 n,分解出其万位 a(a=n/10000)、千位 b(b=n%10000/1000)、百位 c(c=n%1000/100)、十位 d(d=n%100/10)和个位 e(e=n%10),若满足 aaaaa+bbbbb+ccccc+ddddd+eeeee== n,则 n 是 5 位花朵数。
源程序及运行结果
#include <iostream>
using namespace std;
int main()
{
int n, a, b, c,d,e; //n、a、b、c和d分别为5位数自身及其万、千、百、十和个位
for(n=10000 ;n<=99999;n++)
{
a=n/10000;
b=n%10000/1000;
c=n%1000/100;
d=n%100/10;
e=n%10;
if(a*a*a*a*a+b*b*b*b*b+c*c*c*c*c+d*d*d*d*d+e*e*e*e*e==n)
cout<<n<<" ";
}
cout<<endl;
return 0;
}
编译并执行以上程序,得到如下所示的结果。
54748 92727 93084
Press any key to continue
仿照实例 1-2 和 1-3 的方法,我们可以求解出所有的 6 位花朵数、7 位花朵数等等。但是按照上面的方法,都需要修改程序,在程序中相应地增加变量、添加语句。能不能输入一个自然数 N(为简单起见,N<=9,因为 C++ 中 32 位整数的最大值为 2147483647,更多的位数会超过 int 型数据的表数范围),求解出所有的 N 位花朵数呢?
1.3、 水仙花数的进一步连营
【实例 1-4】N 位花朵数
一个 N 位的十进制正整数,如果它的每个位上的数字的 N 次方的和等于这个数本身,则称其为 N 位花朵数。
例如:当 N=3 时,153 就满足条件。当 N=4 时,1634 满足条件。当 N=5 时,54748 满足条件。
实际上,对 N 的每个取值,可能有多个数字满足条件。编写程序,对输入的 N(N<=9),求出所有的 N 位花朵数。
编程思路
为求解 N 位的花朵数,需要对 N 位数 x(x 为 10N-1~10N-1 之间的整数)进行穷举。对每个枚举的 x,求出其每个位上的数字的 N 次方的和 digitsum,若满足 digitsum==x,则 x 是 N 位花朵数。
为求解 N 位花朵数,可以抽象两个函数。一个是 int power(int x,int n),用于求 x 的 n 次方;另一个是 int digitsum(int x,int n),用于求 x 的各位数的 n 次方之和。
源程序及运行结果
# include <iostream>
# include <ctime>
using namespace std;
int power(int x,int n)
{
int i,p=1;
for (i=1;i<=n;i++)
p*=x;
return p;
}
int digitsum(int x,int n)
{
int s=0;
while(x!=0)
{
s+=power(x%10,n);
x=x/10;
}
return s;
}
int main()
{
int n, x,a, b; //a、b分别为相应穷举区间的上下界限
cin>>n;
a=power(10,n-1);
b=power(10,n)-1;
int start=clock();
for(x=a; x<=b; x++)
{
if(digitsum(x,n)==x)
cout<<x<<" ";
}
cout<<endl<<"Running Time :"<<clock()-start<<" ms."<<endl;
return 0;
}
编译并执行以上程序,若输入 5,可得到如下所示的结果。
5
54748 92727 93084
Running Time :16 ms.
Press any key to continue
若输入 7,可得到如下所示的结果。
7
1741725 4210818 9800817 9926315
Running Time :3234 ms.
Press any key to continue
若输入 8,可得到如下所示的结果。
8
24678050 24678051 88593477
Running Time :39282 ms.
Press any key to continue
若输入 9,可得到如下所示的结果。
9
146511208 472335975 534494836 912985153
Running Time :457875 ms.
Press any key to continue
上面的程序可以根据输入的 N,求出所有的 N 位花朵数。但从执行结果可以看出,求 7 位花朵数用时 3234ms,比求 5 位花朵数的 16ms 多得多。求 9 位花朵数更是用时 457875ms。能否想一些办法更快速地求解呢?
实际上,在我们解决问题时,当数据量比较小时,采用什么样的办法都无所谓,因为一般都能快速得到结果;但当数据量比较大时,就需要对解决问题的办法进行优化了,否则,时间上的开销是难以接受的。
2、花朵数的集智
2.1、花朵数的初步集智
在“三国杀”游戏中,武将黄月英有一个技能是“集智”,其技能描述是:每当你使用一张非延时类锦囊,(在它结算之前)你可以立即摸一张牌。也就是说,当黄月英在游戏中打出诸如无中生有、顺手牵羊、过河拆桥、借刀杀人、五谷丰登、南蛮入侵、万箭齐发、决斗、桃园结义、无懈可击、铁锁链环等牌时,可以另外摸一张牌。
借用这个概念,在程序设计实践中,我们设计了一个程序后,可以在这个程序的基础上,再进行优化和扩展,看能否采用另外的、更好的方法来解决这个问题。即采用一种方法解决一个问题后,再采用另外的方法来解决这个问题,也就是打出一张非延时类锦囊牌后,再摸一张牌(看能否找到一种更好的解法)。
下面对实例 1-4 中的程序进行初步的优化。
从给出的程序中可以看出,对于每个枚举的数 x 的每一位(x%10),程序中都会调用 power 函数求它的 n 次方。例如,若输入的是 5,则穷举 90000 个 5 位数(10000~99999),每个数有 5 位,则 power 函数会被调用 450000 次。
实际上,每次调用 power 无非是求 0~9 这 10 个数字中某个数字的 n 次方。因此,若事先调用 power 函数将 0~9 的 n 次方求出来并存放在一个数组中,则在求 x 的各位数字的 n 次方之和时,只需要取用数组中相应的值即可,而无需反复调用 power 函数。这样一定可以缩短求解的时间。
采用这个思路优化后的源程序如下:
#include <iostream>
#include <ctime>
using namespace std;
int table[10];
int power(int x,int n)
{
int i,p=1;
for (i=1;i<=n;i++)
p*=x;
return p;
}
int digitsum(int x,int n)
{
int s=0;
while(x!=0)
{
s+=table[x%10]; // 直接引用表中预先求得的值
x=x/10;
}
return s;
}
int main()
{
int n, x,a, b; //a、b分别为相应穷举区间的上下界限
cin>>n;
a=power(10,n-1);
b=power(10,n)-1;
int start=clock();
for (x=0; x<=9; x++) // 初始化数据表
table[x]=power(x,n);
for(x=a; x<=b; x++)
{
if(digitsum(x,n)==x)
cout<<x<<" ";
}
cout<<endl<<"Running Time :"<<clock()-start<<" ms."<<endl;
return 0;
}
编译并执行以上程序,若输入 7,得到如下所示的结果。
7
1741725 4210818 9800817 9926315
Running Time :984 ms.
Press any key to continue
若输入 8,得到如下所示的结果。
8
24678050 24678051 88593477
Running Time :11125 ms.
Press any key to continue
从执行结果可以看出,采用打表的方法后,求解耗时明显减少了。
2.2、花朵数的进一步集智
上面的程序虽然效率得到了提升,但仍然不理想。例如,执行上面的程序,若输入 9,可得到如下所示的结果。
9
146511208 472335975 534494836 912985153
Running Time :125563 ms.
Press any key to continue
从运行结果可以看出,求 9 位的花朵数,耗时近 2 分钟。下面我们来寻找更好的方法来解决求 N 位花朵数这个问题。
实例 1-3 中求 5 位花朵数是对所有的 90000 个 5 位数(10000~99999)进行穷举,看每个数的各位数字的 5 次方之和是否等于这个数字。实际上,可以穷举 0~9 这 10 个数字出现的次数(每个数字都可能出现 0~5 次),当所有数字出现次数之和等于 5 时,说明这时数字的组合有可能为 5 位花朵数,进而求出每个数字的 5 次方分别乘以其出现的次数的和值 sum,再判断 sum 内各个数字出现的次数是否与穷举各个数字时每个数字出现的次数分别相同,若相同,则 sum 就是一个 5 位花朵数。
例如,当穷举到数字 2 出现了 2 次、7 出现 2 次、9 出现 1 次时,由于 2+2+1=5,此时计算 sum=225+275+1*95=64+33614+59049=92727,检查得到的和 sum 的各个位,若恰好出现 2 个 2、2 个 7 和 1 个 9,说明这种数字组合使得其和为 5 位花朵数。
按这样的思路穷举,只需处理 2002 种情况。这是因为新思路是对数字出现的次数进行穷举。共有 7 种情形:
- 5 位数中每个数字只出现 1 次(即 1+1+1+1+1),有种情况。
- 5 位数中 1 个数字出现 2 次、另 3 个数字各出现 1 次(即 1+1+1+2),有种情况。
- 1+2+2(即 1 个数字出现 1 次,一个数字出现 2 次,还有一个数字出现 2 次)有种情况。
- 1+1+3(即 1 个数字出现 1 次,另一个数字也出现 1 次,还有一个数字出现 3 次)有种情况。
- 1+4(即 1 个数字出现 1 次,另一个数字出现 4 次)有种情况。
- 2+3(即 1 个数字出现 2 次,另一个数字出现 3 次)有种情况。
- 1 个数字出现 5 次,有 10 种情况。
252+840+360+360+90+90+10=2002。
因此,按新思路解决问题的关键在于怎样穷举各数字的出现次数。
【实例 1-5】取数组合
有 0~9 共 10 个数字,现需从这 10 个数字中取出 n 个数字构成一个取数组合,每个数字可以重复取,但不考虑取数顺序。
例如,当 n=3 时,123、132、213、231、312 和 321 均视为同一种取数组合(数字 1、2、3 各取 1 个),而 112(两个 1 和一个 2)和 122(一个 1 和两个 2)是不同的取数组合。
编写程序,输入 n,输出所有的取数组合种数。例如,输入 3,输出 220;输入 5,输出 2002;输入 8,输出 24310。
编程思路 1
定义一个 take 数组来保存 n 位数中每个数字的出现次数。最直接的办法可以编写一个 9 重循环,最外层循环次数从 0~n 用于对 take[0]赋值、第 2 层内循环次数从 0~n-take[0]用于对 take[2]赋值、…、依次类推,最内层循环次数从 0~用于对 take[8]赋值,剩下的次数赋给 take[9]。
源程序 1
#include <iostream>
using namespace std;
int main()
{
int n,i,cnt=0,take[10]={0};
cin>>n;
for (take[0]=0; take[0]<=n; take[0]++)
{
for (take[1]=0; take[1]<=n- take[0]; take[1]++)
{
for (take[2]=0; take[2]<=n- take[1]- take[0]; take[2]++)
{
for (take[3]=0; take[3]<=n- take[2]- take[1]- take[0]; take[3]++)
{
for (take[4]=0; take[4]<=n- take[3]- take[2]- take[1]- take[0]; take[4]++)
{
for (take[5]=0; take[5]<=n- take[4]- take[3]- take[2]- take[1]- take[0]; take[5]++)
{
for (take[6]=0; take[6]<=n- take[5]- take[4]- take[3]- take[2]- take[1]- take[0]; take[6]++)
{
for (take[7]=0; take[7]<=n- take[6]- take[5]- take[4]- take[3]- take[2]- take[1]- take[0]; take[7]++)
{
for (take[8]=0; take[8]<=n- take[7]- take[6]- take[5]- take[4]- take[3]- take[2]- take[1]- take[0]; take[8]++)
{
take[9]=n- take[8]- take[7]- take[6]- take[5]- take[4]- take[3]- take[2]- take[1]- take[0];
for (i=0;i<=9;i++)
cout<<"i:"<<take[i]<<" ";
cout<<endl;
cnt++;
}
}
}
}
}
}
}
}
}
cout<<"Count="<<cnt<<endl;
return 0;
}
编程思路 2
上面的思路虽然直接,但按 9 重循环的写法太累赘。实际上,如果将 take 数组从 take[0]~take[9]赋值看出一个依次赋值的过程,那么这个过程可以抽象为一个递归过程。
设函数 DFS(int take[],int index,int leave)表示对数组元素 take[index]赋予 0~leave 之间每个值,则其后会对数组元素 take[index+1]赋予 0~leave- take[index]之间的每个值,即递归地调用函数 DFS(take[], index+1, leave- take[index])。递归的终止条件为 index==10,即 take[0]~take[9]全部赋了值。递归的初始调用为 index=0、leave=n。
源程序 2 及运行结果
#include <iostream>
using namespace std;
int cnt=0;
void DFS(int take[],int index,int leave)
{
int i;
if(index==9) // 0~8各数字使用个数列举完成
{
take[index]=leave; // 剩余的给数字9
cnt++; // 有效取数组合,计数
return ;
}
for(i=0;i<=leave;i++)
{
take[index]=i;
DFS(take,index+1,leave-i);
}
}
int main()
{
int n, take[10]={0};
cin>>n;
DFS(take,0,n);
cout<<"Count="<<cnt<<endl;
return 0;
}
编译并执行以上程序,若输入 5,可得到如下所示的结果。
5
Count=2002
Press any key to continue
有了上面的取数组合函数 void DFS(int take[],int index,int leave),将 n 位数中 0~9 每个数字出现的次数,存储在 take 数组中后,可以采用循环
sum=0;
for (i=0;i<10;i++)
sum=sum+take[i]*power(i,lenN);
计算出在当前组合情况下各位数字的 lenN 次方之和 sum。再用函数 Judge 判断 sum 中出现的数字组合情况是否与 take 数组中的组合情况相同,若相同,则就是 n 位花朵数。
采用新思路优化后的源程序如下:
#include <iostream>
#include <ctime>
using namespace std;
int table[10];
int power(int num,int n)
{
int p=1,i;
for (i=1;i<=n;i++)
p=p*num;
return p;
}
bool Judge(int take[], int sum)
{
int i,a[10]={0};
while (sum)
{
a[sum%10]++;
sum/=10;
}
for(i=0; i<10 && a[i]==take[i];i++);
return i==10;
}
void DFS(int take[],int index,int leave)
{
int i,sum;
if(index==9) // 0~8各数字使用个数列举完成
{
take[index]=leave; // 剩余的给数字9
sum=0;
for (i=0;i<10;i++)
sum=sum+take[i]*table[i];
if(Judge(take,sum))
{
cout<<sum<<" ";
}
return ;
}
for(i=0;i<=leave;i++)
{
take[index]=i;
DFS(take,index+1,leave-i);
}
}
int main()
{
int n,i,take[10]={0};
cin>>n;
int start=clock();
for (i=0; i<=9; i++) // 初始化数据表
table[i]=power(i,n);
DFS(take,0,n);
cout<<endl<<"Running Time :"<<clock()-start<<" ms."<<endl;
return 0;
}
编译并执行以上程序,若输入 9,可得到如下所示的结果。
9
534494836 472335975 912985153 146511208
Running Time :15 ms.
Press any key to continue
从这个执行结果看出,采用新思路优化后的程序的执行效率好很多。
实际上,我们还可以进一步优化。以 5 位花朵数为例,由于 9 的 5 次方等于 59049,因此 9 最多只能在 5 位花朵数中出现 1 次,否则和值会超过 5 位数,因此可以对和值可能超过 5 位数的情况进行剪枝;另外,当小数字用得太多,还可能导致和值的位数达不到,例如 4 的 5 次方为 1024,5 个 4 的 5 次方的和值才 5120,因此若 5 位数全部由 5 以下的数字组成,5 次方之和不可能达到 5 位数,可以直接进行下次搜索,增加大数字的个数。实验验证,经过两次剪枝后,处理的情况为 1329 种,又会大大减少。并且,如果位数越大,采用两次剪枝后的效率越好。例如,求解 8 位花朵数时,0~9 的 8 位取数组合情况有 24310 种,采用剪枝后,处理的情况为 16131 种。
为进行剪枝处理,在当前数字 index 确定了个数 i 后,将 i*indexn 累加到和 sum 中,为此,可以考虑在 DFS 函数中增加一个参数 sum,用于保存当前和。这样,在剪枝处理时可以引用这个和值。
采用剪枝处理后的源程序如下:
#include <iostream>
#include <ctime>
using namespace std;
int table[12]; // 其中table[10]保存power(10,n),table[11]保存power(10,n-1)
int power(int num,int n)
{
int p=1,i;
for (i=1;i<=n;i++)
p=p*num;
return p;
}
bool Judge(int take[], int sum)
{
int i,a[10]={0};
while (sum)
{
a[sum%10]++;
sum/=10;
}
for(i=0; i<10 && a[i]==take[i];i++);
return i==10;
}
void DFS(int take[],int index,int leave,int sum)
{
int i,tmp,tmp1;
if(index==10) // 0~9各数字使用个数列举完成
{
if(leave>0) return; // 位数不足,直接返回
if(Judge(take,sum))
{
cout<<sum<<" ";
}
return ;
}
for(i=0;i<=leave;i++)
{
take[index]=i;
tmp=sum+i*table[index];
if (tmp>=table[10]) break; // 剪枝1,和值已超过power(10,n)
tmp1=tmp+(leave-i)*table[9]; // 剩余的数字全用9的话
if(tmp1<table[11]) continue; // 剪枝2,小数字太多,和值不可能达到位数
DFS(take,index+1,leave-i,tmp);
}
}
int main()
{
int n,i,take[10]={0};
cin>>n;
int start=clock();
for (i=0; i<=9; i++) // 初始化数据表
table[i]=power(i,n);
table[10]=power(10,n);
table[11]=power(10,n-1);
DFS(take,0,n,0);
cout<<endl<<"Running Time :"<<clock()-start<<" ms."<<endl;
return 0;
}
3、 21 位花朵数
在前面优化程序的基础上,下面我们来解决“蓝桥杯”软件设计大赛的一道比赛题。这是一个大 Boss 哟!
【实例 1-6】21 位花朵数
编写一个程序,输出所有的 21 位花朵数。(注意:这个整数有 21 位,它的各位数字的 21 次方之和正好等于这个数本身。)
编程思路
按照上面的剪枝优化程序的思路,可以求 21 位花朵数。由于 21 位整数超出了 long 整型数的范围,因此采用大整数来处理。定义结构体 BigNum 如下:
struct BigNum
{
int dig[4];
int len;
};
其中,数组元素 dig[0]~dig[3]分别保存大整数从低位到高位的数字,为节省空间,每个数组元素保存大整数中的 8 位数字。即存储大整数时,从低位到高位每 8 位 1 组,每组保存到一个数组元素中。Len 表示每 8 位 1 组的组数,意即大整数的位数(不过以 8 位为 1 个单位)。
在大整数的基础上,编写如下 6 个函数,来进行大整数的处理。
void Init(BigNum &a); // 大整数a初始化为0
void PrintBigNum(BigNum a); // 输出大整数 a
BigNum CarryUp(BigNum a); // 大整数 a 的处理进位
BigNum Multi(BigNum a,int n); // 大整数 a 和整数 n 相乘
int Cmp(BigNum a,BigNum b); // 大整数a和b比较大小
BigNum Add(BigNum a,BigNum b);// 大整数a 和 b 相加
另外,由于求 1 个数字的 21 次方也较耗时。为了减少程序运行时间,可以先把 0~9 的 21 次方及其不同的出现次数的值先算出来,存储到指定的数组中。
定义数组 BigNum pow[10]; ,其中 pow[i](0<=i<=9)存储数字 i 的 21 次方。
定义数组 BigNum sp[10][22]; ,其中 sp[i][j] 存储数字 i 的 21 次方乘以 j(出现次数)得到的值。
这样,程序中需要用到这些值时,可直接引用相应数组元素的值,从而最多只需计算 10 个大数连加(想想为什么?),无需计算求幂和乘法,大大节约时间。
源程序及运行结果
#include <iostream>
#include <ctime>
using namespace std;
const int BIT=100000000; // 每8位一组
struct BigNum
{
int dig[4];
int len;
};
BigNum pow[10],MAX,MIN;
BigNum sp[10][22];
int take[10]={0};
int LEN=21;
void Init(BigNum &a)
{
a.len=1;
for (int i=0;i<4;i++)
a.dig[i]=0;
}
void PrintBigNum(BigNum a)
{
int i;
cout<<a.dig[a.len-1];
for(i=a.len-2; i>=0; i--)
{
cout.fill('0'); // 定义填充字符'0'
cout.width(8); cout<<a.dig[i];
}
cout<<endl;
}
BigNum CarryUp(BigNum a) // 处理进位
{
int i;
for(i=0;i<a.len;i++)
{
a.dig[i+1]+=a.dig[i]/BIT;
a.dig[i]%=BIT;
}
return a;
}
BigNum Multi(BigNum a,int n)
{
BigNum c;
int i;
Init(c);
c.len=a.len+1;
for(i=0;i<a.len;i++)
{
c.dig[i]=(a.dig[i])*n;
}
c=CarryUp(c);
if(c.len>0 && c.dig[c.len-1]==0)c.len--;
return c;
}
int Cmp(BigNum a,BigNum b)
{
if(a.len>b.len) return 1;
if(a.len<b.len) return -1;
int i;
for(i=a.len-1;i>=0 && a.dig[i]==b.dig[i];i--);
if(i==-1) return 0;
return a.dig[i]-b.dig[i];
}
BigNum Add(BigNum a,BigNum b)
{
int i;
if(b.len>a.len) a.len=b.len;
for(i=0;i<a.len;i++)
{
a.dig[i]+=b.dig[i];
}
a=CarryUp(a);
if(a.dig[a.len]) a.len++;
return a;
}
bool Judge(BigNum sum)
{
int aa[10]={0};
int i,j;
for(i=1;i<=8;i++) // 求 sum 中0~9各个数字出现的次数,保存到数组aa中
for (j=0;j<3; j++)
{
aa[sum.dig[j]%10]++;
sum.dig[j]/=10;
}
aa[0]=aa[0]-3;
for(i=0; i<10 && aa[i]==take[i];i++);
return i==10;
}
void DFS(int deep,BigNum Sum,int leave)
{
BigNum check;
BigNum cc;
int i;
if(deep==10)
{
if(leave>0)return;
if(Judge(Sum))
{
PrintBigNum(Sum);
}
return ;
}
for(i=0;i<=leave;i++)
{
take[deep]=i;
check=Add(Sum,sp[deep][i]);
if(Cmp(check,MAX)>=0) break; // 剪枝1
cc=Add(check,sp[9][leave-i]);
if(Cmp(cc,MIN)<0) continue; // 剪枝2
DFS(deep+1,check,leave-i);
}
}
int main()
{
int start=clock();
int i, j;
BigNum sum;
Init(pow[0]);
for(i=1;i<10;i++)
{
Init(pow[i]); pow[i].dig[0]=1;
for (j=1;j<=21;j++)
pow[i]=Multi(pow[i],i);
}
for(i=0;i<10;i++)
Init(sp[i][0]);
for(j=0;j<10;j++)
for(i=1;i<22;i++)
{
sp[j][i]=Add(sp[j][i-1],pow[j]);
}
Init(sum);
MAX.dig[2]=100000; MAX.len=3;
MIN.dig[2]=10000; MIN.len=3;
DFS(0,sum,LEN);
cout<<endl<<"Running Time :"<<clock()-start<<" ms."<<endl;
return 0;
}
编译并执行以上程序,得到如下所示的结果。
128468643043731391252
449177399146038697307
Running Time :32046 ms.
Press any key to continue
在进行程序设计实践时,一定不能就题论题,而应该贯穿着“连营”和“集智”。在这个实践过程中,有提出问题、解决问题、扩展问题、再解决问题、对解决问题的方法评价、优化设计等几个环节,实际上是一个螺旋式滚动向前的过程,在这个螺旋式不断向前的过程中,能够非常自然地调动程序设计学习者的学习兴趣,而且通过问题的不断扩展“连营”,有效地开阔读者的思维。这种通过一个程序的层层推进,不断连营,进行程序设计训练的方法,本质上是一个循序渐进、螺旋式上升的过程,可使学习者在“走台阶”的过程中,进入到程序设计的殿堂。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)