邮局发行一套票面有n(0<n<=10)种不同面值的邮票。若限每封信所贴的邮票张数不得超过m枚,存在整数r使得用不超过m(0<m<=2n)枚的邮票,可以贴出连续整数1,2,3,…,r值来,找出这种面值数,使得r值最大。
一行,输入N及M
输出R
2 3
7
搜索,回溯
DFS+回溯法。
邮票问题搜索的状态应该是已经确定的邮票面值(各不相同并且总数不超过
n)和它们能够贴出的最大连续邮资区间,以此来枚举下一个可能的邮票面值。因
此,很自然地,使用原题中的标识符,数组 x 记录当前已经确定的邮票面值,整
数 r 表示当前使用不超过 m 张邮票能贴出的最大连续邮资区间。
对于第 i 层的结点,x[1…i]表示当前已经有 i 个面值确定,r 表示由 x[1…i]能
贴出的最大连续区间。
现在,要想把第 i 层的结点往下扩展,有两个问题需要解决:
1 ,哪些数有可能成为下一个的邮票面值,即 x[i+1]的取值范围是什么;
2 ,对于一个确定的 x[i+1],如何更新 r 的值让它表示 x[1…i+1]能表示的最
大连续邮资区间。~
第一个问题很简单, x[i+1]的取值要和前 i 个数各不相同,最小应该是 x[i] + 1,
最大就是 r+1,否则 r+1 没有办法表示。我们现在专注第二个问题。
第二个问题自己有两种思路:
一,计算出所有使用不超过 m 张 x[1…i+1]中的面值能够贴出的邮资,然后从
r+1 开始逐个检查是否被计算出来。
二,从 r+1 开始,逐个询问它是不是可以用不超过 m 张 x[1…i+1]中的面值贴
出来。
第二种方法,由于前面 i 种邮票不能贴出 r+1,因此贴 r+1 必须要 i+1 种邮票
进入,但是 i+1 种邮票需要多少张呢? 又替换哪一些呢 ? 这并不很好实现,
我们换一个角度思考,还是按照方法 1 着手。
第二个问题第一种思路:
定义数组 y[postage]表示用当前的面值 postage 贴出某个邮资所需要的最少
的邮票数(初始时都是无穷大); S(i)表示 x[1…i]中不超过 m 张邮票的贴法的集
合,S(i)中元素的值就是它所表示的贴法贴出来的邮资,于是,可以把 S(i)中的元
素按照它们的值的相等关系分成 k 类。
第 j 类表示贴出邮资为 j 的所有的贴法集合,用 T(j)表示, T(j)有可能是空集,
例如对于{1,2,4},T(7)为空集,T(8)={{4,4}}。
此时有:S(i) = T(1) U T(2) U T(3) U … U T(k),U 表示两个集合的并。
现在考虑 x[i+1]加入后对当前状态 S(i)的影响。假设 s 是 S(i)中的一个元素,
即 s 表示一种合法的贴法,
x[i+1]对 s 能贴出的邮资的影响就是 x[i+1]的多次重复增加了 s 能贴出的邮资。
这样说是因为有两种情况不需要考虑:
一,从 s 中去掉几张邮票,把 x[i+1]加进去,这没有意义,因为从 s 中去掉几
张邮票后 s 就变成了 S(i)中的另一个元素 t,我们迟早会对 t 考虑 x[i+1]的影响。
二,将 x[i+1]加入 s,同时再把 x[1]也加入 s(如果 s 中还能再贴两张邮票的话),
这也没有意义,原因同一。
所以, x[i+1]对 s 的影响就是,如果 s 中贴的邮票不满 m 张,那就一直贴 x[i+1],
直到 s 中有 m 张邮票,这个过程会产生出很多不同的邮资,它们都应该被加入
到 S(i+1)中。因为 s 属于 S(i),它也必定在某个 T(k)中,而 T(k)中能产生出最多不
同邮资的是 T(k)中用的邮票最少的那个元素。
至此,原题中的解法就完全出来了:用数组 x 记录当前已经确定的邮票面值,
用 r 表示当前最大的连续邮资区间,用数组 y 表示用当前的面值贴出某个邮资所
需要的最少的邮票数。前 i 种面值的邮票,最大贴出邮资 [ 0, x[i]*m ] 。增加一
种新面值 x[i+1] 的邮票,在前(i)种邮票面值最大可能取到的邮资区间范围内进行,
y[ ]数组更新操作,如果前 i 种邮票贴出面值为 postage 的最少邮票张数 num
用f[i]表示要凑出总值为i所需要的最少的单张邮票数量,用a[i]存当前情况下第i张邮票的面值,用dfs枚举前i张邮票,当i>n是说明已经不能再深搜了,那么就更新答案maxr,如果还可以深搜,就在a[i-1]+1与dp(i-1)+1中枚举a[i]的值,dp(i)为寻找在目前已有的i张邮票中可以凑出的最大r值;
#include
#include
#include
#include
using namespace std;
int n,m,a[100],maxr,f[10000];
void init()
{
cin>>n>>m;
a[1]=1;
}
int DP(int n)
{
int i,j;
f[0]=0;
for(i=1;;i++)//枚举总值
{
f[i]=-1;//f[i]存要凑出总值为i的最少需要的邮票数
for(j=1;j<=n;j++)//枚举邮票面值a[j]
if(i-a[j]>=0/*a[j]可以用来凑i*/ && f[i-a[j]]+1<=m/*加上后邮票数不超过m*/ &&(f[i]==-1 || f[i-a[j]]+1<=f[i])/*可以更新f[i]的值*/)
f[i]=f[i-a[j]]+1;//更新f[i]的值
if(f[i]==-1)//凑不出来
return i-1;//
}
}
void DFS(int p)
{
if(p>n)//如果面值大于n,那么就不能再往下dfs,已经到了更新答案的时候了
{
int s=DP(n);//n种邮票面值可以凑出的最大值
if(s>maxr)
{
maxr=s;
}
return;
}
int i;//如果还可以往下深搜
for(i=a[p-1]+1;i<=DP(p-1)+1;i++)//枚举可以新放的邮票的值,从a[p-1]->目前最大邮票单张面额,dp(p-1)+1->下一张最大单张邮票面额,也就是枚举a[p]可以有的值
{
a[p]=i;//更新此时a[p]的值
DFS(p+1);//对此时已有邮票的单张面额进行深搜,更新a[p+1]的值,或者更新maxr
}
}
int main()
{
init();
DFS(2);
cout<