原题链接:[NOIP1999 提高组] 邮票面值设计 - 洛谷
目录
题目描述
解题思路:
代码实现:
题后总结:
题目描述
给定一个信封,最多只允许粘贴 N 张邮票,计算在给定 K(N+K≤15)种邮票的情况下(假定所有的邮票数量都足够),如何设计邮票的面值,能得到最大值 MAX,使在 1 至 MAX 之间的每一个邮资值都能得到。
例如,N=3,K=2,如果面值分别为 1 分、4 分,则在 1∼6 分之间的每一个邮资值都能得到(当然还有 8 分、9 分和 12 分);如果面值分别为 1 分、3 分,则在 1∼7 分之间的每一个邮资值都能得到。可以验证当 N=3,K=2 时,7 分就是可以得到的连续的邮资最大值,MAX=7,面值分别为 1 分、3 分。
输入格式
2 个整数,代表 N,K。
输出格式
输出共 2 行。
第一行输出若干个数字,表示选择的面值,从小到大排序。
第二行,输出 MAX=S
, 表示最大的面值。
输入输出样例
输入 #1
3 2
输出 #1
1 3 MAX=7
解题思路:
此题主要利用了dp+dfs,根据题目来看需要k种邮票,那么需要哪几种邮票,这就需要枚举,dfs去每一步搜索,题目中要求最大连续长度,考虑dp去解决,要求最大连续长度,意思就是通过这k种面值,能够连续不间断组成1~?之间的每一个数,那么我定义一个f[M]表示通过k种面值得到分值为M的最少邮票张数,只要这个值小于题目中给定的邮票张数n,那么从小到达遍历,此位置前面的都是连续的,当找到第一个不满足<n的分数i时,那么它前面i-1都是连续的,即此方案的最大连续长度为i-1,如果找不到这样的条件,那么能组成的值都是小于n的,最大连续长度就是最大的邮票值*n。
那么我们重新顺一下解题思路,首先枚举这k种邮票面值组合,我们解决办法是最简单的dfs,我们枚举出这k种面值组合,对它进行求解最大连续长度,处理办法是dp,具体解释注释在代码上。
代码实现:
#include<iostream>
#include<cstring>
using namespace std;
const int N=20;
const int inf=0x3f3f3f;
int n,k,res;//res保存答案
int a[N],ans[N],f[50000];//f[i]拼面值为i所需要最小张数
//a[i]组成的分数中间过渡数组,ans[i]满足条件的答案分数数组
int dp(int t,int mx){f[0]=0;//初始化特例for(int i=1;i<=a[t]*n;i++)f[i]=inf;//初始化正无穷for(int i=1;i<=k;i++){//k种位数for(int j=a[i];j<=a[t]*n;j++){//最大连续长度至少以自己分数为起始,最大到填满n张最大的面值a[t]f[j]=min(f[j],f[j-a[i]]+1); //类似于背包更新}}for(int i=1;i<=a[t]*n;i++){if(f[i]>n){//若此时填的面值张数大于邮票可以贴的最大张数,就说明此时i不满足,前i-1是满足的return i-1;}}return a[t]*n;//若前面都成立那么最大连续分数就是最大值分数*张数
}
void dfs(int dep,int x){//dep种分数组成x为连续最大长度 if(dep==k+1){//当分数种数为k种,满足条件if(x>res){//此时最大连续长度大于res满足更新条件res=x;//更新for(int i=1;i<=k;i++){ans[i]=a[i];}}return;}for(int i=a[dep-1]+1;i<=x+1;i++){//为了不避免重复,至少是上一位的分数值+1,最大到x+1,否则前面的也会不连续a[dep]=i;int t=dp(dep,x);//t更新最大连续长度dfs(dep+1,t);}
}
int main(){cin>>n>>k;dfs(1,0);//从1开始去找,第一个分必是1,初始最大长度为0for(int i=1;i<=k;i++)cout<<ans[i]<<" ";cout<<endl;cout<<"MAX="<<res<<endl;return 0;
}
题后总结:
此题博主感觉难度较大,如果题量够大,可能对他们来说,一眼就出思路,dfs+dp这种题第一次做,当时看的是罗老师的B站讲解,大家看不懂的可以去看看。
信奥编程罗老师:P1021 [NOIP1999 提高组] 邮票面值设计_哔哩哔哩_bilibili
注:图片来自罗老师讲解。