看了各种大神的代码后,终于知道怎么写了。
参考
http://blog.csdn.net/shuangde800/article/details/7755452
http://blog.csdn.net/jcwkyl/article/details/4137398
与第一个链接对应的两种解法
暴力
首先开一个数组stampVal【0...i】来保存各个面值,再开一个maxVal[0...i]来保存当前所有面值能组成的最大连续面值。
那么,我们可以确定stampVal[0] 一定是等于1的。因为如果没有1的话,很多数字都不能凑成。
然后相应的,maxVal[0] = 1*h. h为允许贴邮票的数量
接下去就是确定第二个,第三个......第k个邮票的面值了,这个该怎么确定呢?
对于stampVal[i+1], 它的取值范围是stampVal[i]+1 ~maxVal[i]+1.
stampVal[i]+1好理解, 这一次取的面值肯定要比上一次的面值大, 而这次取的面值的上限是上次能达到的最大连续面值+1, 是因为如果比这个更大的话, 那么
就会出现断层, 即无法组成上次最大面值+1这个数了。 举个例子, 假设可以贴3张邮票,有3种面值,前面2种面值已经确定为1,2, 能达到的最大连续面值为6, 那么接下去第3种面值的取值范围为3~7。如果取得比7更大的话会怎样呢? 动手算下就知道了,假设取8的话, 那么面值为1,2,8,将无法组合出7 !!
知道了这个以后,就可以知道回溯的大概思路了, 但是还不够, 怎样取得给定的几个面值能够达到的最大连续面值呢?
最直观容易想到的就是直接递归回溯枚举所有情况, 便可知道最大连续值了。
由于这道题的数据量很小,所以这样做完全不会超时,速度也不错:
#include <iostream>
#include <cstring>
#include<cstdio>
#include<cstdlib>
#include <string>
#include<algorithm>
#include<cmath>
#define set0(a) memset(a, 0, sizeof(a))
//#define MARK -2147483647
using namespace std;
int n,kind;
int ans[20],ansmax;
int maxstamp[20],stampkind[20];int you[1000];
void qiuzhi(int zhang,int cur,int sum,int kind)
{
int i;
if(zhang==n)return;
if(cur>kind)return;
for(i=0;i<=n;++i)
{ if(zhang+i<=n)
{ you[sum+i*stampkind[cur]]=1;
qiuzhi(zhang+i,cur+1,sum+i*stampkind[cur],kind);
}
}
}
void sear(int cur)
{
int i;
if(cur>=kind)
{ //cout<<maxstamp[cur-1]<<endl;
if(maxstamp[cur-1]>ansmax)
{
ansmax=maxstamp[cur-1];
for(i=0;i<cur;++i)
{
ans[i]= stampkind[i];
}
}
return;
}
for(i=stampkind[cur-1]+1;i<=maxstamp[cur-1]+1;++i)
{
stampkind[cur]=i;
you[0]=1;
set0(you);
qiuzhi(0,0,0,cur);
int maxs;
int ii;
for(ii=1;;++ii)if(you[ii]!=0)maxs=ii;else break;
maxstamp[cur]=maxs;
sear(cur+1);
}
}
int main()
{
// freopen("in.txt","r",stdin);
while(scanf("%d%d",&n,&kind))
{
if(!n&&!kind)break;
stampkind[0]=1;
maxstamp[0]=n;
ans[0]=1;
ansmax=n;
sear(1);
int i;
for(i=0;i<kind;++i)
{
printf("%3d",ans[i]);
}
printf(" ->");
printf("%3d\n",ansmax);
}
return 0 ;
}
计算X[1:i]的最大连续邮资区间在本算法中被频繁使用到,如果数据量大的话,很可能就会超时了。
《计算机算法设计与分析第三版》给了一个更高效的方法:
第二个问题自己有两种思路:一,计算出所有使用不超过m张x[1…i+1]中的面值能够贴出的邮资,然后从r+1开始逐个检查是否被计算出来。二,从r+1开始,逐个询问它是不是可以用不超过m张x[1…i+1]中的面值贴出来。
两种思路直接计算其计算量都是巨大的,需要借助动态规划的方法。模仿0-1背包问题,假设S(i)表示x[1…i]中不超过m张邮票的贴法的集合,这个集合中的元素数目是巨大的,例如,只使用1张邮票的贴法有C(i+1-1,1)=C(i,1)=i种,使用2张邮票的贴法有C(i+2-1,2)=C(i+1,2)=i*(i+1)/2种,……,使用m张邮票的贴法有C(i+m-1, m)种,其中C(n,r)表示n个元素中取r个元素的组合数。于是,S(i)中的元素的数目总共有C(i+1-1, 1) + C(i+2-1,2)+ … + C(i+m-1,m)个。S(i)中的每个元素就是一种合法的贴法,对应一个邮资。当前最大连续邮资区间为1到r,那么S(i)中每个元素的邮资是不是也在1到r之间呢?不一定,比如{1,2,4},当m=2时,它能贴出来8,但不能贴出来7,这一点自己在写代码时犯了错误。总之,在搜索时,一定要保持状态的一致性,即当深度搜索到第i层时,一定要确保用来保存结点状态的变量中保存的一定是第i层的这个结点的状态。言归正传,定义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表示用当前的面值贴出某个邮资所需要的最少的邮票数。状态结点的转换过程已经在上面说的非常清楚了。现在只差写代码了。代码如下:
dp
#include <iostream>
#include <cstring>
#include<cstdio>
#include<cstdlib>
#include <string>
#include<algorithm>
#include<cmath>
#define set0(a) memset(a, 0, sizeof(a))
#define MARK 2147483647
using namespace std;
int n,kind;
int ans[20],ansmax;
int maxstamp[20],stampkind[20];int you[1001];
//void qiuzhi(int zhang,int cur,int sum,int kind)
//{
// int i;
// if(zhang==n)return;
// if(cur>kind)return;
// for(i=0;i<=n;++i)
// { if(zhang+i<=n)
// { you[sum+i*stampkind[cur]]=1;
// qiuzhi(zhang+i,cur+1,sum+i*stampkind[cur],kind);
// }
// }
//}
void sear(int cur)
{
int i;
if(cur>=kind)
{ //cout<<maxstamp[cur-1]<<endl;
if(maxstamp[cur-1]>ansmax)
{
ansmax=maxstamp[cur-1];
for(i=0;i<cur;++i)
{
ans[i]= stampkind[i];
}
}
return;
}
int cun[1001];
memcpy(cun,you,sizeof(you));
for(i=stampkind[cur-1]+1;i<=maxstamp[cur-1]+1;++i)
{
stampkind[cur]=i;
// set0(you);
// qiuzhi(0,0,0,cur);
int maxs;
int ii;int add;
for(ii=0;ii<stampkind[cur-1]*n;++ii)
if(you[ii]<n)
for(add=1;add<=n-you[ii];++add)
if(you[ii+add*i]>add+you[ii])
{you[ii+add*i]=add+you[ii];}
for(ii=maxstamp[cur-1];;++ii)if(you[ii]!=MARK)maxs=ii;else break;
maxstamp[cur]=maxs;
sear(cur+1);
memcpy(you,cun,sizeof(cun));
}
}
int main()
{
// freopen("in.txt","r",stdin);
while(scanf("%d%d",&n,&kind))
{
if(!n&&!kind)break;
stampkind[0]=1;
maxstamp[0]=n;
ans[0]=1;
ansmax=n;
int i;
for(i=0;i<=1000;++i)
you[i]=MARK;
for(i=0;i<=n;++i)
you[i]=i;
sear(1);
for(i=0;i<kind;++i)
{
printf("%3d",ans[i]);
}
printf(" ->");
printf("%3d\n",ansmax);
}
return 0 ;
}