整币兑零是一个特殊的分解统计案例,其不同的的兑换种类与零币的种类及各零币的具体数值密切相关;
本节探讨特定的6种零币与一般从键盘输入的m种零币的两类兑零统计;
特定整币兑零
把一张1元整币兑换成1分,2分,5分,1角,2角和5角共6种零币,共有多少种不同兑换种数?
一般地,把一张二元整币,5元整币或一张n元整币兑换成1分,2分,5分,1角,2角和5角共6种零币,共有多少种不同兑换种数?
1.说明:
一般地设整币的面值为n个单位,面值为1、2、5、10、20、50单元零币的个数分别为p1、p2、p3、p4、p5、p6;
显然需要解一次不定方程:
- p1+2*p2+5*p3+10*p4+20*p5+50*p6=n
其中p1、p2、p3、p4、p5、p6为非负整数;
对这六个变量实施枚举,确定枚举范围为:
0<=p1<=n,0<=p2<=n/2,0<=p3<=n/5;
0<=p4<=n/10,0<=p5<=n/20,**0<=p6<=n/50
**;
在以上枚举的6重循环中,若满足条件p1+2*p2+5*p3+10*p4+20*p5+50*p6=n,则为一种兑零方法,输出结果并通过变量m统计不同的兑换种类;
2.程序设计:
#include<stdio.h>
#include<math.h>
int main()
{
int p1,p2,p3,p4,p5,p6,n;
long m;
printf("请输入整币量n:");
scanf("1分 2分 5分 1角 2角 5角 \n");
for(p1=0;p1<=n;p1++)
for(p2=0;p2<=n/2;p2++)
for(p3=0;p3<=n/5;p3++)
for(p4=0;p4<=n/10;p4++)
for(p5=0;p5<=n/20;p5++)
for(p6=0;p6<=n/50;p6++)
if(p1+2*p2+5*p3+10*p4+20*p5+50*p6==n) /*根据条件检验*/
{
m++;
printf("%5d%5d%5d",p1,p2,p3);
printf("%5d%5d%5d\n",p4,p5,p6);
}
printf("%d(1,2,5,10,20,50)=%ld \n",n,m);
}
3.程序运行示例及其注意事项:
请输入整币量n:100
1分 2分 5分 1角 2角 5角
0 0 0 0 0 2
0 0 0 0 5 0
0 0 0 1 2 1
......
100(1,2,5,10,20,50)=4562
共有4562个解,即有4562种不同的兑换种数。
注意:当输入数值过大时,兑换种数相应越多,时间也就越长;
精简枚举循环设计
1.说明:
在上述程序的6重循环中,我们可精简p1循环,在循环内应用:
- p1=n-(2*p2+5*p3+10*p4+20*p5+50*p6)
p1赋值,如果p1为非负数,对应一种兑换法;
当n较大时程序运行时间较长,主要是每一个解都要打印,如果只需要计算兑换种数,则可省略打印语句,这样可大大缩减程序的运行时间;
2.程序设计:
#include<stdio.h>
#include<math.h>
int main()
{
int p1,p2,p3,p4,p5,p6,n;
long m;
printf("请输入整币量n:");
scanf("%d",&n);
for(p2=0;p2<=n/2;p2++) /*已省略p1循环*/
for(p3=0;p3<=n/5;p3++)
for(p4=0;p4<=n/10;p4++)
for(p5=0;p5<=n/20;p5++)
for(p6=0;p6<=n/50;p6++)
{
p1=n-(2*p2+5*p3+10*p4+20*p5+50*p6); /*p1为一分币的个数*/
if(p1>=0)
m++; /*用m统计兑换种数*/
}
printf("%d(1,2,5,10,20,50)=%ld \n",n,m);
}
3.程序运行示例及其注意事项:
请输入整币量n:200
200(1,2,5,10,20,50)=69118
进一步优化枚举设计
1.说明:
以上程序的循环次数已经大大精简,进一步分析,可以看到在程序的循环设计设置中p3循环可从0~n/5改进为0~(n-2*p2)/5,因为在n中p2已占去了2*p2,以此类推,对p4、p5、p6的循环可作类似的循环参量优化;
2.程序设计:
#include<stdio.h>
int main()
{
int p1,p2,p3,p4,p5,p6,n;
long m=0;
printf("请输入整币量n:");
scanf("%d",&n);
for(p2=0;p2<=n/2;p2++)
for(p3=0;p3<=(n-2*p2)/5;p3++)
for(p4=0;p4<=(n-2*p2-5*p3)/10;p4++)
for(p5=0;p5<=(n-2*p2-5*p3-10*p4)/20;p5++)
for(p6=0;p6<=(n-2*p2-5*p3-10*p4-20*p5)/50;p6++)
{
p1=n-(2*p2+5*p3+10*p4+20*p5+50*p6);
if(p1>=0)
m++; /*用m统计兑换种数*/
}
printf("%d(1,2,5,10,20,50)=%ld \n",n,m);
}
3.程序运行示例及其注意事项:
请输入整币量n:500
500(1,2,5,10,20,50)=3937256
注意:以上3个设计尽管都是枚举,但循环的设置与循环参量的改进可精简去一些不必要的比较操作,可大大缩减程序的运行时间;
一般整币兑零
把整币兑零的零币一般化为m种,每一种零币值从键盘输入;
1.说明:
因为零币的种类较多时,应用枚举显然不能胜任,考虑应用递推求解,应用递推求解的关键在于寻求递推关系;
设整币为n个单位,m种指定零币从小到大分别为x1,x2,……,xm个单位,整币兑零实际上是一个整体数无序可重复化零问题;
记a(j,i)为整体数是i,最大零数是xj的化零种数,当去掉一个xj后,整体数变为p=i-xj,最大零数可为x1,或x2,……,或xj(因为可重复),于是有递推式:
- a(j,i)=a(1,p)+a(2,p)+……+a(j,p) (其中p=i-xj)
可据整体数i能否被x1整除确定初始条件:
- a(1,i)=1 (当i能被x1整除时)
- a(1,j)=0 (当i不能被x1整除时)
作以上函数递推,分别计算得a(1,n),a(2,n),……,a(m,n),求和即得所求的整币兑零种数:
- n(x1,x2,……,xm)=a(1,n)+a(2,n)+……+a(m,n)
应用函数递推简化了化零的难度;
2.程序设计:
#include<stdio.h>
int main()
{
int p,i,j,n,m,k;
static int x[12];
static long int a[12][1001];
long b,s;
printf("请输入整币值(单位数):"); /*输入处理数据*/
scanf("%d",&n);
printf("请输入零币种数:");
scanf("%d",&m);
printf("(从小到大依次输入每种零币值)\n");
for(i=1;i<=m;i++)
{
printf("第%d种零币值(单位数):",i);
scanf("%d",&x[i]);
}
for(i=0;i<=n;i++) /*确定初始条件*/
if(i%x[1]==0)
a[1][i]=1;
else
a[1][i]=0;
for(s=a[1][n],j=2;j<=m;j++) /*递推计算a(2,n),a(3,n),...*/
{
for(i=x[j];j<=n;i++)
{
p=i-x[j];
b=0;
for(k=1;k<=j;k++)
b+=a[k][p];
a[j][i]=b;
}
s+=a[j][n]; /*累加a(1,n),a(2,n),...*/
}
printf("整币兑零种数为:%ld\n",s); /*输出兑零种数*/
}
3.程序运行示例及其注意事项:
请输入整币值(单位数):1000
请输入零币种数:9
(从小到大依次输入每种零币值)
第1种零币值(单位数):1
第2种零币值(单位数):2
第3种零币值(单位数):5
第4种零币值(单位数):10
第5种零币值(单位数):20
第6种零币值(单位数):50
第7种零币值(单位数):100
第8种零币值(单位数):200
第9种零币值(单位数):500
整币兑零种数为:327631321
这一问题如果应用前面的枚举设计求解,显然难以胜任;
注意:本程序是求解整币兑零,事实上输入的整币值并不限于实际的100、500、200、1000等,可输入234、5017等任意整数“整币值”,输入的零币值也不受实际约束,只要小于整币值的任意整数即可;