一道題深度理解回溯法--連續郵資問題


#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;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM