一道题深度理解回溯法--连续邮资问题

#include<iostream>
#include<cstring> 
using namespace std;
//连续邮资问题
/*
某国发行了n种不同面值的邮票
并规定每封信最多允许贴m张邮票
在这些约束下,为了能贴出{1,2,3,... ,maxvalue}连续整数集合的所有邮资,并使maxvalue的值最大
应该如何设计各邮票的面值?
例如,当n=5、m=4时,面值设计为{1,3,11,15,32},可使maxvalue达到最大值70
或者说,用这些面值的1至4张邮票可以表示不超过70的所有邮资,但无法表示邮资71
而用其他面值的1至4张邮票如果可以表示不超过k的所有邮资,必有k<=70
*/

//第一种邮票一定是面值为1的邮票,不然邮资1无法构造
//如果前x[1->i]种邮票在不超过m张的前提下能构造的邮资序列为1->r
//则第x[i+1]种邮票面值的可选范围是x[i]+1->r+1

int n,m;//n种邮票贴m张
int maxstamp;//最大邮资
int r;//前x[1->cur]种邮票能支付的连续邮资的最大值 
const int MM=500;//邮票能支付的最大邮资不超过MaxMoney 
const int MN=50;//邮票的种类不超过MaxN
int x[MN];
//x[i]=k表示选用第i中邮票的面值为k,这是一个邮票面值依次递增的数组
int y[MM];
//y[j]=k表示前x[1->cur]种邮票支付邮资j时邮票的最少张数为k
int ans[MN];//达到最大邮资的一组邮票面值
const int inf=10000;//y[j]=inf用来表示邮资j不可达 
void backtrace(int cur){//寻找下标为cur的邮票面值
    if(cur>=n){
        if(r>maxstamp){
            maxstamp=r;
            for(int i=0;i<n;i++){
                ans[i]=x[i];
            }
        }
        return;
    }
    int backup_y[MM];
    for(int i=0;i<MM;i++){
        backup_y[i]=y[i];
    }
    int backup_r=r;
    for(int i=x[cur-1]+1;i<=r+1;i++){
        x[cur]=i;//选取第cur张邮票的面值为i
        for(int k=0;k<=x[cur-1]*(m-1);k++){
            if(y[k]>=m)continue;//k范围的含义是邮资从0到x[cur-1]*(m-1)的最少邮票数有可能小于m张 
            //邮资大于x[cur-1]*(m-1)的最少邮票数现阶段一定大于等于m张 
            for(int num=1;num<=m-y[k];num++){//用已有的最少邮票组合加上新选取的cur张邮票去更新y数组 
                if(k+x[cur]*num<MM){//邮资不能越界 
                    y[k+x[cur]*num]=min(y[k+x[cur]*num],y[k]+num); 
                    //如果不超过m张的新的邮票组合构成的邮资对应的张数比新组合的张数大,则更新它 
                } 
            } 
        } 
        while(y[r+1]<inf)r++; 
        backtrace(cur+1);
        //回溯
        for(int i=0;i<MM;i++){
            y[i]=backup_y[i];
        } 
        r=backup_r;
        //回溯代表选取x[cur]=i结束,状态返回到cur-1时,并且接着for循环为cur选取新的i值 
    }
} 
void print(){
    cout<<maxstamp<<endl;
    for(int i=0;i<n;i++){
        cout<<ans[i]<<" ";
    }
    cout<<endl;
} 
int main(){
    memset(y,inf,sizeof(y));
    cin>>n>>m;
    x[0]=1;//x数组下标从0开始表示第1种邮票
    y[0]=0;//支付邮资0需要0张邮票
    r=m;//目前只有1种邮票面值为1,因此能支付的范围是1->m
    for(int i=1;i<=m;i++)
        y[i]=i; //一种邮票的情况下支付邮资i的最少邮票数为i 
    backtrace(1);//寻找下标为1的邮票面值,即第二种邮票面值 
    print();//输出结果 
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/lyt888/p/12602152.html