题目描述
给定一个信封,最多只允许粘贴N张邮票,计算在给定M(N+M< =10)种邮票的情况下(假定所有的邮票数量都足够),如何设计邮票的面值,能得到最大max ,使得1~max之间的每一个邮资值都能得到。
例如,N=3,M=2,如果面值分别为1分、4分,则在l分~6分之间的每一个邮资值都能得到(当然还有8分、9分和12分):如果面值分别为1分、3分,则在1分~7分之间的每一个邮资值都能得到。可以验证当N=3,M=2时,7分就是可以得到连续的邮资最大值,所以MAX=7,面值分别为l分、3分。
样例输入:共一行,两个整数,分表为N与M的值。输入数据
一行,分别为 N,M 。
输出数据
两行。
第一行为 m 种邮票的面值,按升序排列,各数之间用一个空格隔开。
第二行为最大值。样例输入
3 2
样例输出
1 3 MAX=7
解析:
1、当邮票面值a[1:M]已知,根据当前邮票可以求出最大maxv ,使得1~maxv之间的每一个邮资值都能得到。
这里的状态方程为:dp[i] 表示 maxv 为 i 所需要的最少的邮票数。 dp[i]=min(dp[i-a[j]]+1),j=(1->M)
for(int j=1;j<=m && a[j]<=i;j++)
dp[i] = min(dp[i-a[j]]+1, dp[i]);
2、当前邮票未知,采用深度搜索方式枚举可能的邮票面值组合。a[j] 的枚举的范围是 a[j-1]+1 ~ maxv+1 (其中maxv为 前 j-1 个邮票能组成的最大连续值)
void dfs(int num){
if(num>= m){ //当收集到m个邮票面值,进行验证
int c = cale(m); //当前邮票面值组合能够组成的最大连续值
if(c <= maxv) return;
for(int i = 1;i <= m; i++) //当能够到达的连续值比当前maxv大,更新邮票面值
ans[i] = a[i];
maxv = c; //更新最大值
return;
}else{
int temp = cale(num); //前num个邮票能够组合的最大连续值
for(int i = temp + 1;i > a[num]; i--){
a[num + 1] = i;
dfs(num+ 1);
}
}
}
3、动态规划的实现
int cale(int num){
if(!num) return 0;
memset(dp, 0x7f, sizeof(dp));
dp[0] = 0;
int i = 0;
while(dp[i]<=n){
i++;
for(int j=1;j<=num && a[j]<=i; j++)
dp[i] = min(dp[i-a[j]] + 1, dp[i]);
}
return i-1;
}
4、完整代码
#include<iostream>
#include<cstring>
using namespace std;
int a[15];
int ans[15];
int dp[1001];
int n, m;
int maxv = 0;
int cale(int num){
if(!num) return 0;
memset(dp, 0x7f, sizeof(dp));
dp[0] = 0;
int i = 0;
while(dp[i]<=n){
i++;
for(int j=1;j<=num && a[j]<=i; j++)
dp[i] = min(dp[i-a[j]] + 1, dp[i]);
}
return i-1;
}
void dfs(int num){
if(num >= m){
int c = cale(m);
if(c <= maxv) return;
for(int i = 1;i <= m; i++)
ans[i] = a[i];
maxv = c;
return;
}else{
int temp = cale(num);
for(int i = temp + 1;i > a[num]; i--){
a[num + 1] = i;
dfs(num + 1);
}
}
}
int main(){
cin >> n >> m;
dfs(0);
cout<<ans[1];
for(int i = 2;i <= m;i++)
cout<<" "<<ans[i];
cout<<endl;
cout<<"MAX="<<maxv;
return 0;
}