/*
连续邮资问题
算法设计:
该问题是设计最佳的邮票面值,用来表示最大的区间
对于连续邮姿问题,用n元组x[1:n]表示n种不同的邮票面值并约定它们从小到大排列。
整数r表示当前使用不超过m张邮票能贴出的最大连续邮资区间。
x[1] = 1是唯一的选择。此时最大连续邮资区间是[1:m]
接下来x[2]的可能取值范围是[2:m+1],这个取值范围就决定了x[2]应该怎么选,
只所以确定这样的范围是因为,至少要保证增加一个面值后,可以将区间增
大1一般情况下,已选定x[1:i-1],此时最大连续邮资区间是[1:r],则接下来
x[i]的可取值范围是[x[i-1]+1:r+1]。由此可以看出,在用回溯法时,可以用一棵树表示其
解空间。该解空间树中各结点的度随x的不同取值而变化。
设x[1~n]是从小到大排列的n种邮票面值。
首先x[1]=1 是毫无疑问的,否则最小邮资 1 无法支付。
面值为1的邮票构成的最大连续区间只能是[1,m] (只贴1张就是1……m张邮票全贴满是m)。
如果这时候再给我们第二种面额的邮票。因为它需要比x[1]大,所以最少只能是2;
又因为单纯由x[1]支付的邮资区间最大只到m,再往上就出现了断档,所以x[2]最大也只能取m+1
(如果再大的话,m+1这个值就无法正常表示)
同样的道理,假设前面k种邮票面值都已经有了,并且能构成[1, r]的连续邮资区间,
那么第k+1种邮票的面值必须满足:
x[k]+1 <= x[k+1] <= r+1
算法就是要找到这么多种可能情况下的最优方案
对于连续邮资问题,用n元组x[1:n]表示n种不同的邮票面值,并约定它们从小到大排列。
x[1] = 1是惟一的选择。在未确定剩余其它n-1种邮票面值的情况下,只用x[1]一种邮票面值,
所能得到的最大连续邮资区间是[1:m]。接下来,确定x[2]的取值范围,很明显,
x[2]的值最小可以取到2,因为x[1] 这时已经为1了。那么最大可以取的值呢?应当是m+1,
为什么?因为如果x[2]取m+2,则在这个方案中,邮资m+1不可能取得,
(这个时候,x[2:n]的任何一个面值是不能取的,因为就算只取一张,总和也会至少为m+2,
超过m+1了,反过来,如果只取x[1]面值,则就算把m张全取了,也只能凑到m)。所以,
在搜索第2张邮票的时候,搜索范围是2 ~ m+2(m+2不剪取)。在一般的情况下,已选定x[1:i-1],
最大连续邮资区间是[1:r]时,接下来x[i]的可取值范围是[x[i-1]+1 :r+1]。由此可以看出,
[x[i-1]+1 :r+1](为限界函数)在用回溯法解连续邮资问题时,可用树表示其解空间,
该解空间树中各结点的度随x的不同取值而变化。
现在可以大致画出该解空间树的结构。
现在的关键问题是,如何确定x3的取值范围?转换一下就是如何求得在x[1],x[2]面值确定的情况下,
用不超过m张邮票,所得到的最大连续邮资区间是多少?如果每一个这样的问题能够确定,
则解空间树已经构造好了(每一个的儿子结点为r-x[i-1]),我们就可以通过深度优先遍历的方式得到最佳面值设计
(每到达叶结点见比较更新)。
现在来看在x[1],x[2]面值确定的情况下,用不超过m张邮票,所得到的最大连续邮资区间是多少?
最大连续邮资区间总是以1作为起点的,所以我们用max来表示这个最大值,显然,max至少可以取到m,
因为即使不用x[2]面值,只用x[1]面值的情况下,所能得到的最大值就已经是m了。现在来看在x[1],
x[2]面值确定的情况下,用不超过m张邮票,max+1能不能取到?
假设在拼凑的过程中,x[2]面值的邮票取t张,则t>=0,t<=(max+1) /x[2] && t<=m
现在x[2]面值的邮票张数已经确定,原问题转化为另一个子问题,即,在x[1]面值确定的情况下,
用不超过m-t张邮票,max+1 – t*x[2] 能不能取到? 。。。。。。
到这一步就很容易理解了,因为x[1] 面值为1,所以如果要拼凑的值max+1- t*x[2] <= m-t的话,
则只用取max+1- t*x[2]张就可以拉。相反,如果max+1- t*x[2] > m-t的话,则是不可以满足的。
在下面的回溯法描述中,递归函数Backtrack实现对整个解空间的回溯搜索。
maxvalue记录当前已经找到的最大连续邮资区间,bestx是相应的当前最优解。
数组y用来记录当前已经选定的邮票面值x[1:i]能贴出各种邮资所需的最少邮票数。
也就是说,y[k]是用不超过m张面值为x[1:i]的邮票,贴出邮资k所需的最少邮票张数。
在函数Backtrack中,
当i>n时,表示算法已经搜索到一个叶结点,得到一个新的邮票面值设计方案x[1:n]。如果该方案能贴出的
最大连续邮资区间大于当前已经找到的最大连续邮资区间maxvalue,则更新当前最优值maxvalue和相应的最优解。
当i <= n时,当前扩展结点z是解空间中的一个内部结点,在该结点处x[1:i-1]
能贴出的最大最大邮资区间为r-1.因此在结点z处x[i]的可取范围是x[i-1]+1:r,
从而,结点z有r-x[i-1]个儿子结点。算法对当前扩展结点z的每一个儿子结点,
以深度优先的方式递归地对相应子树进行搜索
解空间是多叉树,孩子接点个数是每层都在变化的
*/
#include<iostream>
#include<Windows.h>
using namespace std;
class Stamp
{
friend int MaxStamp(int ,int ,int []);
private:
// int Bound(int i);
void Backtrack(int i,int r);
int n;//邮票面值数
int m;//每张信封最多允许贴的邮票数
int maxvalue;//当前最优值
int maxint;//大整数
int maxl;//邮资上界
int *x;//当前解
int *y;//贴出各种邮资所需最少邮票数
int *bestx;//当前最优解
};
void Stamp::Backtrack(int i,int r)
{
/*计算X[1:i]的最大连续邮资区间,考虑到直接递归的求解复杂度太高,
我们不妨尝试计算用不超过m张面值为x[1:i]的邮票贴出邮资k所需的最少邮票数y[k]。
通过y[k]可以很快推出r的值。事实上,y[k]可以通过递推在O(n)时间内解决*/
int j,k;
for(j=0;j<=x[i-2]*(m-1);j++) //x[i-2]*(m-1)是第i-2层循环的一个上限,目的是找到r-1的值
if(y[j]<m)
{
for(k=1;k<=m-y[j];k++) //k是对表示j剩余的票数进行检查
{
if(y[j]+k<y[j+x[i-1]*k])
//x[i-1]*k是k张邮票能表示的最大邮资
//+j表示增加了i邮资后能
//判断新增加的能表示的邮资需要多少
{
y[j+x[i-1]*k]=y[j]+k;
//对第i-2层扩展一个x[i-1]后的邮资分布
}
}
}
//查看邮资范围扩大多少,然后查询y数组从而找到r
while(y[r]<maxint) //计算X[1:i]的最大连续邮资区间
{
r++;
}
//搜索求出r-1的值,对应x[1:i-1]的在m张限制内的最大区间
if(i>n) // 如果到达发行邮票的张数,则更新最终结果值,并返回结果
{
if(r-1>maxvalue) // 用r计算可贴出的连续邮资最大值,而maxStamp存放最终结果
{
maxvalue=r-1;
for(int j=1;j<=n;j++)
bestx[j]=x[j]; // 用x[i]表示当前以确定的第i+1张邮票的面值,bestx保存最终结果
}
return;
}
int *z=new int[maxl+1];
for(k=1;k<=maxl;k++)
z[k]=y[k];
//保留数据副本,以便返回上层时候能够恢复数据
//以上都是处理第i-1层及其之上的问题
for(j=x[i-1]+1;j<=r;j++) //在第i层有这么多的孩子结点供选择
{
x[i]=j;
Backtrack(i+1,r);//返回上层恢复信息
for(int k=1;k<=maxl;k++)
y[k]=z[k];
}
delete[] z;
}
int MaxStamp(int n,int m,int bestx[]){
Stamp X;
int maxint=32767;
int i,maxl=1500;
X.n=n;
X.m=m;
X.maxvalue=0;
X.maxint=maxint;
X.maxl=maxl;
X.bestx=bestx;
X.x=new int [n+1];
X.y=new int [maxl+1];
for(i=0;i<=n;i++)
X.x[i]=0;
for(i=1;i<=maxl;i++)
X.y[i]=maxint;
X.x[1]=1;
X.y[0]=0;
X.Backtrack(2,1);
cout<<"当前最优解:";
for(i=1;i<=n;i++)
cout<<bestx[i]<<" ";
cout<<endl;
delete[] X.x;
delete [] X.y;
return X.maxvalue;
}
int main(){
int *bestx;
int n;
int m;
cout<<"请输入邮票面值数:";
cin>>n;
cout<<"请输入每张信封最多允许贴的邮票数:";
cin>>m;
bestx=new int[n+1];
for(int i=1;i<=n;i++)
bestx[i]=0;
cout<<"最大邮资:"<<MaxStamp(n,m,bestx)<<endl;
Sleep(5000);
return 0;
}