邮票问题,副答案

Huaraco 2003-10-17 04:03:32
1、邮票问题
设有已知面额的邮票m种,每种有n张,用总数不超过n张的邮票,能从面额1开始,最多连续组成多少面额.(1<=m<=100,1<=n<=100,1<=邮票面额<=255) 输入:n m A[1..m](面额) 输出:MAX=(最大面额值) Input1:4 1 2 4 Output1:10 一看到这个题目,给人的第一感觉是用回溯算法,从面额1开始,每种面额都用回溯进行判断,算法复杂度并不高,但是当m,n取到极限值100时,程序明显超时,因此,回溯算法在这里并不可取. 能否用递推完成呢?我们有一个思路:从面额1开始,建立递推关系方程,就用范例来说吧,面额1,2,4只用1张邮票行了,面额3可以表示为面额1,2的邮票和1+1=2,面额5有两种表示方式MIN(面额1+面额4,面额2+面额3),照此类推,递推关系方程不难建立,由于1<=邮票面额<=255,1<=n<=100,因此MAX值最多可达到25500,25500次循环里必定还有嵌套循环,因此算法不加优化,很难在规定时间内得出最优值.这就需要递推的算法优化. 一味递推不寻求算法优化,速度较之搜索提高不少,但一旦数据规模过大,很难在规定时间内得出最优值.就拿邮票问题来说,以下是递推的一种方法:

{$A+,B-,D-,E+,F-,G-,I-,L-,N-,O-,P-,Q-,R-,S-,T-,V-,X+,Y-} {$M 16384,0,655360}
program stamps;
var f,fout:text; n,m,i,j,k:integer;
c:array [1..255] of integer; {面额}
a:array [1..31000] of integer; {递推数组}
bl:boolean;
procedure readfile; {读文件}
begin
assign(f,'STAMP.DAT');
reset(f); assign(fout,'STAMP.OUT');
rewrite(fout);
readln(f,n);
readln(f,m);
bl:=true;
for i:=1 to m do
begin
readln(f,c[i]);
if c[i]=1 then bl:=false;
end;
close(f);
end;
procedure work;
begin
if bl=true then write(fout,'MAX=0') {不存在面额1时输出无解}
else begin
i:=1; a[i]:=1;
/**************************************************/
repeat
i:=i+1;
for j:=1 to m do
if ((i mod c[j]=0) and ((i div c[j])<a[i]))
or (a[i]=0) {判断它能否被题目给定面额整除} then
a[i]:=i div c[j];
for j:=1 to trunc(i/2) do
if (a[j]+a[i-j]<a[i]) then a[i]:=a[j]+a[i-j]; {寻找(1<=j<=i),使A[j]+A[i-j]值最小}
until (a[i]>n) or (a[i]=0);
/**************************************************/
write(fout,'MAX=',i-1); {输出}
end;
close(fout);
end;
begin
readfile;
work;
end

两个/**************************************************/间的内容我看不明白,能解释一下吗?
...全文
319 6 打赏 收藏 转发到动态 举报
写回复
用AI写文章
6 条回复
切换为时间正序
请发表友善的回复…
发表回复
yushang0824 2003-10-20
  • 打赏
  • 举报
回复
请写一个程序找出在一个信封所能贴上的邮票空间内,最大能连续凑出多少面额,并且列出所有能凑出此面额的邮票贴法。

最大能连续凑出多少面额, 什么意思?面额最大吗? 什么叫连续?看懂你的题目呢<

daiwei1852 2003-10-20
  • 打赏
  • 举报
回复
不懂,我是阿菜,真的看不懂!
like1283620 2003-10-20
  • 打赏
  • 举报
回复
如果你懂C语言的话,可以参考一下上面的程序,不懂就算了
like1283620 2003-10-20
  • 打赏
  • 举报
回复
你的问题在算法上可以解决,但是你必须限定面值的数量,要不然用C语言编的程序的循环量很大,我初步做了以下分析(第一建立数学模型,第二算法的实现):
第一建立数学模型:
假设最多可以贴n张邮票,设有k种这样的面值:x1,x2,x3,x4,………,xk(再假设x1为最大的面值,xk为最小的面值),设每种面值有:a1,a2,a3,a4,…………ak张,
那么有下列关系式:
xk<= x1*a1+x2*a2+x3*a3+x4*a4+………+xk*ak <=n*x1
a1,a2,a3,a4,…………ak的取值范围是[0,n];
a1+a2+a3+a4+………+ak<=n
第二算法的实现:
基于上面的数学模型用C语言可以实现如下:
#define MAX 1000
main()
{
int I,n; int cnt=0,j=0;
int aa[MAX],bb[MAX];
int x1,x2,x3,x4,………,xk;
scanf(“%d%d%d………%d”,&n,&x1,&x2,&x3,………,&xk);\\输入最多贴的张数n和面值数据其中x1最大,xk最小
int a1,a2,a3,a4,…………ak;
for(I=xk;I<=n*xk;I++)
{
for(a1=0;a1<=n;a1++)
for(a3=0;a2<=n;a2++)
for(a3=0;a3<=n;a3++)
for(a4=0;a4<=n;a4++)
…………………………
for(ak=0;ak<=n;ak++)
if((I==x1*a1+x2*a2+x3*a3+x4*a4+………+xk*ak)&&( a1+a2+a3+a4+………+ak<=n))
{aa[cnt]=I;cnt++;}
}
for(I=0;I<cnt-1;I++)
if(aa[I]!=aa[I+1])
{bb[j]=aa[I];j++;}
bb[j]=n*x1;
for(I=0;I<j+1;I++)
printf(“%d,”,bb[I]);
}

从程序打印的结果就可以看出来你想要的结果!
jhyu 2003-10-17
  • 打赏
  • 举报
回复
参考下面的文章:

【问题】贴邮票
  有一个国家有n种面额的邮票,且当地的信封有m个贴邮票的空间。现在我们要凑出从1开始连续的面额,请写一个程序找出在一个信封所能贴上的邮票空间内,最大能连续凑出多少面额,并且列出所有能凑出此面额的邮票贴法。
  输入一共有两行。第一行包含两个正整数n、m,意义如上所述,其中n≦50,m≦35。第二行有n个正整数,分别代表n种邮票的面额。输出的第一行是能连续凑出的最大面额,接下来请列出在不超过m张邮票的限制下,所有能凑出此面额的方法。输出时请按照凑出此面额所需要的张数由小到大排序;若张数相同时,则按照最小的邮票面额由小到大排序;输出每种方法时请按照用到的邮票面额由小到大列出。

《输入范例》
  4 5
  1 4 12 21

《输出范例》
  71
  4 4 21 21 21
【想法】
  考了那么多次试,这回终于出现除了最短路径之外的DP题了。虽然这题也是要我们列出所有的方法,不过这次得先用所谓的「动态规画(Dynamic Programming)」来求出最大值。笔者的作法是:我们要凑出i元时,只要取之前已找出的i-c元的最少邮票的凑法(其中c为某一个小于i的邮票面额)加上c元的邮票即可,因此我们只要将所有的c取遍,再取其中的最小值即为i元的最少邮票的凑法。这时只要检查张数是否已超过上限,如果超过,就表示无法凑出i元;因此只需从1元开始做,直到找到这样的i,那么i-1就是我们所要的答案了。
  找到最大值后,再来就是用DFS找出所有的凑法了。不过题目说凑法的输出要按照张数排序,难道我们还要把所有的解全部记下来吗?虽然这可以开出够大的数组存起来,但我们根本不必这样做!因为很容易就可以证出所有的凑法一定必须用完m个空间!为什么呢?首先,题目说从1开始凑出连续面额,因此一定有一种邮票的面额为1元,否则就无解了。假设可连续凑出的最大面额为k,如果我们找到一种少于m张邮票的凑法,那么我们可以在剩下的空间贴上1元的邮票,这时就凑出了k+1元,则k元就不是可连续凑出的最大面额了。
  另外在DFS暴搜时,虽然测试数据不大,可是递归35层还是会有时间爆掉的危险,这时我们可以用先前所建立的「最小张数凑法」的表格来做cut,如果发现剩余的空间不足以凑出剩下的钱时,就可以提早返回上一层啦!

  以下为笔者所撰写之程序代码(仅供参考):
#include
#include

int n,m;
int cost[50];
int stamp[1751]; // 凑出某一面额所需的最小邮票张数
int stack[35]; // DFS记录凑法的堆栈

int compare(const void *arg1,const void *arg2);
void dfs(int money,int space,int min);

int main(){
int i,j,min;
scanf("%d %d",&n,&m);
for(i=0;i<=1750;i++){ // DP找出凑出i元所需的最小邮票张数
min=0;
for(j=0;j<=i;j++){
if(stamp[i-cost[j]]+1<=m){ // 若找出的方法未超过张数上限
if(!min || min>stamp[i-cost[j]]+1) // 且尚未找到其它方法或找到张数更小的方法
min=stamp[i-cost[j]]+1; // 则更新min值
}
}
stamp[i]=min;
if(!min) // 若无法凑出i元,则最大连续面额为i-1
break;
}
printf("%d\n",i-1);
dfs(i-1,0,0); // DFS找出凑i-1元的方法
return 0;
}

int compare(const void *arg1,const void *arg2){ // qsort用的比较函式,由小到大排序
return *(int *)arg1-*(int *)arg2;
}

void dfs(int money,int num,int min){ // 找出所有凑法的DFS函式
int i; // 处理剩余money元时第num格贴上第min种之后的邮票
if(money==0){ // 若已凑出最大面额则列出方法
for(i=0;im-num) // 若剩余空间不足以凑出money元则返回上一层
return;
for(i=min;i<=money;i++){
stack[num]=cost[i]; // 将第num格填入第i种邮票
dfs(money-cost[i],num+1,i); // 递归凑出剩余的面额
}
}

Huaraco 2003-10-17
  • 打赏
  • 举报
回复
难道没人能看懂吗?

69,369

社区成员

发帖
与我相关
我的任务
社区描述
C语言相关问题讨论
社区管理员
  • C语言
  • 花神庙码农
  • 架构师李肯
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧