• 全部
  • 问答

连续邮资问题.

间谍 长春市野狼科技有限公司 CTO  2001-08-03 07:29:12
加精
假设某国家发行了n种不同面值的邮票并且规定每张信封上最多只允许贴m张.连续邮资问题要求对于给定的n和m的值,给出邮票面值的最佳设计,使得可在1张信封上贴出从邮资1开始,增量为1的最大连续邮资区间.例如:当n=5,m=4时,面值为(1,3,11,15,32)的五种邮票可以贴出邮资的最大连续邮资区间是1到70.
当时选拔赛我就这道题没做上,现在还是不会,请高手指点.听说要用回朔法,不过我还是不会.请把算法说清楚.
...全文
397 点赞 收藏 13
写回复
13 条回复
切换为时间正序
当前发帖距今超过3年,不再开放新的回复
发表回复
starfish 2001-08-03
这道题目出得确实不好。这种NP问题一般没有好办法,只能尽量多拿分。
回复
laozi 2001-08-03
我真搞不懂,李开复博士为什么不出这道题目。
容我点儿时间,好好想想。
回复
starfish 2001-08-03
厉害,居然用QBasic参赛,佩服呀佩服。
QBasic很吃亏的。我觉得DOS下面还是用turbo pascal 7.0 最好,TC3也不错,TC2就太差了:(
回复
间谍 2001-08-03
版主的网址?
回复
间谍 2001-08-03
请问哪有关于这个的详细介绍?
回复
frman 2001-08-03
:))来蹭点参与分:  

  这个题就是搜啊搜啊,剪啊剪啊:)呵呵~~~~不过俺这方面不强,以前做的程序也不够快,这个题海星大哥发给过我他做的东西,虽然我还留着,不过就让海星大哥贴他的解析吧:)不过我看这好象刘汝佳没来(他喜欢ID就用自己名),而我也见过他站上写的这个东西的解析,贴出来看看吧:)

*****************************

第四题 邮票面值设计(40分)
给定一个信封,最多只允许粘贴N张邮票,计算在给定K(N+k<=40) 种邮票的情况
下(假定所有的邮票数量都足够),如何设计邮票的面值,能得到最大max ,使得1-
max之间的每一个邮资值都能得到。
例如,N=3,K=2,如果面值分别为1分、4分,则在l分-6分之间的每一个邮资值
都能得到(当然还有8分、9分和12分):如果面值分别为1分、3分,则在1分-7分之间的
每一个邮资值都能得到。可以验证当N=3,K=2时,7分就是可以得到连续的邮资最大值
,所以MAX=7,面值分别为l分、3分。
样例:
INPUT
N=3 k=2
OUTPUT
1 3
MAX=7

不知同学们第一个感觉是不是递推?反正我当时是。我想了一会儿,发现递推是行不通的,
然后一个很自然的思路就是搜索。当时我很不想用搜索,因为上限是K=40,N=40,但后来才知道
这是出题者的一个疏忽,根本不可能在时限内到40的。从下面的测试数据中也可以看出来。
解状态是一个K元组(v1,v2,v3..vk),不妨设:v1v[p],而若v[p]>=R+2,
则R+1根本不可能取到。
递归搜索就可以了。
本题的难点是如何计算最大连续值(以下成为Q值)。
一个容易想到的方法是枚举所有的取法,求出可以取到的最大值,再求Q值,简单,但是效率不高。
以下是供参考的递推其他方法:
递推求Q值,保存前P个面值用1,2,3..K张可以取得的值,再加上第P+1张取与不取的情况可以得到前P+1个面值
用1,2,3..K张可以取得的值。即,用T张前P个面值能得到S,用T+1张前P+1个面值可以得到S+V[P+1],用T张前
P+1个面值也能得到S.
为了使程序易懂,我采用的是简单的方法,效率低一些,但是好懂一些。以下我竞赛时写的程序,后来加了
很多注释,将就看一下吧!
我先把可能不好懂的地方说一下:
1.Cont(x)就是前x个元素的Q值
2.Find(Findstart,Contstart,x); x:当前搜索的邮票序号,findstart:第x张邮票的最小可能值(搜索起点),
Contstart:需要从Contstart开始连续取值(也就是:前x-1张可以从1取到Contstart-1)
OK,Here it is:

{$A+,B-,D+,E+,F-,G-,I-,L+,N-,O-,R-,S-,V+,X-}
{$M 65520,0,655360}

Program Stamp;
Const
Maxn=40;
Var
Value,BestValue : Array[1..Maxn] Of Integer;
Max,N,K : Integer;

{If N=5,K=5 Value[]=(1,4,9,31,51), It returns 126, Got it?}
Function Cont(x:Integer):Integer;
Var
I:Integer;
CanGet:Array[0..10000] Of Boolean;
Count:Array[1..Maxn] Of Integer;

Procedure UseIt(Num,Left:Integer);
Var
I:Integer;
Total:Integer;
Begin
If Num=x+1 Then
Begin
Total:=0;
For I:=1 to x Do Inc(Total,Value[i]*Count[I]);
CanGet[Total]:=True;
Exit;
End;
For I:=0 to Left Do
Begin
Count[Num]:=I;
UseIt(Num+1,Left-I)
End;
End;
Begin
Fillchar(CanGet,SizeOf(CanGet),0);
UseIt(1,n);
I:=1; While CanGet[i] Do Inc(I); Cont:=I-1;
End;

(*
This is the most important part
eg: N=3,K=3
Suppose we're searching for the 3rd stamp while the first two are 1,4
We got:
1 = 1
2 = 1+1
3 = 1+1+1
4 = 4
5 = 1+4
6 = 1+1+4
Then we call:

Find(5,7,3); that means, we should find a value for the 3rd stamp

from 5,6,7,8..., the first two stamps could get value 1..7-1.

Got it?
*)


Procedure Find(FindStart,ContStart,x:Integer);
Var
I:Integer;
ContMost:Integer;
Begin
If x=k+1 Then
Begin
If Max < ContStart-1 Then
Begin
Max := ContStart-1; {Hey, ContStart is NOT the largest number we really got!}
For I:=1 to K Do BestValue[I]:=Value[I]; {Copy to BestValue}
End;
Exit;
End;

I:=FindStart;
While True Do
Begin
Value[x]:=i;
ContMost:=Cont(x); {Calculate}
Find(I+1,ContMost+1,x+1);
If I+1>ContStart Then Exit Else Inc(I);
{if I+1>ContStart we CANNOT get value Constart 'cause I will be bigger and bigger}
End;
End;

Procedure Init;
Begin
Max:=0; {Initialize}
Write('N='); Readln(N);
Write('K='); Readln(K);
Value[1]:=1; {The 1st Stamp Must Be 1 ^_^}
End;

Procedure Answer;
Var I:Integer;
Begin
For I:=1 to K Do Write(BestValue[I],' ');
WriteLn;
WriteLn('MAX=',Max);
End;

Begin
Init;
Find(2,n+1,2); {Search From the 2nd Stamp}
Answer;
End.


*************************

555555~~~~Pascal看不懂:)

回复
one_add_one 2001-08-03
我的程序也通过了所有数据,也没有超时,并且我当时用的是QBASIC!

只是当时在考场上被N+k<=40吓住了!
回复
frman 2001-08-03
转载(STARFISH) : :)

那道题目还是比较有意思的,我开始想的复杂了,花了挺长的时间的。因为听你说超时,于是想到可能搜索不可行,就想找到递归公式,用类似于动态规划的思想解决。后来确实找到递归公式了,可是那个递归公式非常之不好,不但求解麻烦,而且在最坏的情况下退化成了搜索的指数复杂度!结果我又全部重写,简单的用深度优先做了一下,发现根本没有超时(呵呵,上了你的当了)。五个测试数据(包括那个例子),全部只用了11秒左右,其中最后一个用了10秒多,其他的4个1秒都不到,应该不算超时吧?我记得时间是20秒。我的机器配置是C300Hz+256MB+win2000。简单说一下我的算法,基本上就是深度优先搜索,假设k张邮票分别为stamp[0..k-1], 显然第一张stamp[0]=1,而且stamp[i] >= stamp[i-1]+1, 另外,很关键的一点是,在搜索第 i 张邮票的时候,如果前面的 i-1 张邮票用总数不超过n张最多拼出 1.. max ( i-1) 的面值来来,则第 i 张邮票的面值一定不能超过max(i-1) +1,因为如果第i张邮票的面值超过了这个max(i-1)+1,则无法拼出max(i-1)+1的面值来。所以我在搜索第i张邮票的时候,搜索范围是stamp[i-1] ~ value[i-1]+1 ,其中我用value[i]表示前面的stamp[0] ~stamp[i]张邮票不超过n张最多能拼出的面值,即前面说的max(i)。最后value[k-1]就是所求,因为下标从0开始,所以是k-1,我很不习惯C的这种下标从0开始的规定,有时候会产生歧义:(。

我将代码写好后改编了一下,现在在TC2.0下面调试通过了,具体的看注释吧。事实上,如果将深度优先改成广度优先可以进一步提高效率,因为上面stamp[i]的下限还可以改进

****************************************

这个题说什么N+K〈=40说大了~~~~给你看看测试数据吧:)

N K
7 3 1 8 13 MAX=69
7 4 1 5 24 37 MAX=165
10 3 1 10 28 MAX=146
5 5 1 4 9 31 51 MAX=126

***********************

我的程序就只有第一个和第三个测试数据能在一秒出,第二个得7,8秒吧:) 55555~~~~~最后一个测试数据我去喝了口水上个厕所还没出~~~~毛了吧??呵呵~~好象用了个把分钟,嘿嘿。。。。如果那老师仁慈点的话,40分的题俺还能拿30分:)

回复
starfish 2001-08-03
虽然这是一个NP问题,但是主要考察的是搜索和剪枝。我的那个程序通过了所有的测试数据,并且没有超时。
回复
one_add_one 2001-08-03
什么N+k<=40,当时害我想了半天,浪费了宝贵的时间!
回复
starfish 2001-08-03
呵呵,自由无限,我当时给你发的信早就删掉了,你要是还保存着的话就贴出来吧。我现在只能找到代码了,基本思路就是搜索,确定好搜索的上下界就可以了。这题算是比较容易的题。


#include <stdio.h>
#include <string.h>
#include <assert.h>

#define INPUT_FILE "stamp.in" // input file name
#define OUTPUT_FILE "stamp.out" // output file name
#define MAX_N 40

#define true 1
#define false 0

FILE *fin, *fout; // inout file and output file
int ProbN = 0; // the No. of input case
int n, k;

/*
stamp is current solution,
value[i][j] is the max value that using j pieces of stamp[0]..stamp[i] will get
best is the best solution
*/
int stamp[MAX_N], value[MAX_N], best[MAX_N];

int MaxValue; // the max value of the best solution

int ReadCase()
{
if (feof(fin))
return false;

/*
TO DO: add some code here to read input data
*/
fscanf(fin, "%d%d", &n, &k);
if (n <= 0)
return false;

ProbN++;
return true;
}


int CanGet(int depth, int Y, int total)
{
if (depth == 0)
return (Y <= total);

for (int t=0; (t <= Y/stamp[depth]) && (t <= total); t++)
if ( CanGet(depth-1, Y-t*stamp[depth], total-t))
return true;
return false;
}

void CalValue(int depth)
{
value[depth] = value[depth-1];
while (CanGet(depth, value[depth]+1, n))
value[depth]++;
}

void search(int depth)
{
if (depth == k) {
if (value[k-1] > MaxValue ) { // record the best solution
memcpy(best, stamp, k*sizeof(int));
MaxValue = value[k-1];
}
return;
}

stamp[depth] = stamp[depth-1] + 1;
while (stamp[depth] <= value[depth-1]+1)
{
CalValue(depth);
search(depth+1);
stamp[depth]++;
}

}

void SolveCase()
{
MaxValue = 0;
stamp[0] = 1;
value[0] = n;
search(1);
fprintf(fout, "Case #%d\n", ProbN);
for (int i=0; i<k; i++)
fprintf(fout, "%d ", best[i]);
fprintf(fout, "max = %d\n", MaxValue);
}



int main()
{
assert( fin = fopen( INPUT_FILE, "r" ) );
assert( fout = fopen( OUTPUT_FILE, "w" ) );

while (ReadCase())
SolveCase();

fclose(fin);
fclose(fout);
return 0;
}
回复
one_add_one 2001-08-03
NP问题!

出这题的人,脑子有问题!
回复
Zig 2001-08-03
出这道题目会死人的,这不是人算的。
回复
相关推荐
发帖
数据结构与算法
创建于2007-08-27

3.2w+

社区成员

数据结构与算法相关内容讨论专区
申请成为版主
帖子事件
创建了帖子
2001-08-03 07:29
社区公告
暂无公告