蓝桥杯算法提高VIP-邮票面值设计

题目

题目链接

题解

DFS+动态规划。


整体思路:
i个邮票的面值的范围为:value[i] = value[i-1]+1 ~ mx[i-1]+1,其中value[i]表示第i个邮票的面值,mx[i]表示前i个邮票能构成的连续最大数;解释一下这个范围,第i个邮票的面值至少比第i-1个邮票的面值多1,且必须要不超过前i-1个邮票(默认都是最多n张)所能构成的最大面值+1,要是大于最大面值+1就无法满足连续了。
因此,如果我们知道前一个邮票的面值,那么我们就可以推出当前邮票的面值范围,枚举范围内的面值,进行深搜,当找完k个面值的邮票时,更新一下最大连续数即可。显然,我们现在知道第一个邮票的面值必然是1,因此,上述的深搜方式确实可以进行。

还有一个问题,如何确定mx[i]呢?
我们不去直接考虑从前i种邮票中选取n张能构成的最大面值,而是考虑前i种邮票构成面值为j的最少张数。
即使得到最小张数了,我们如何确定mx[i]?对于前i种邮票,我们只需要从小到大遍历面值j,判断枚举到的j对应的最少张数是否大于nn如题),只要遇到了大于n的面值j,就说明无法用n张构成面值j,不满足连续的要求,所以最多也就能构成面值为j-1了,我们就得到了前i-1种邮票选取最多n张所能构成的最大面值,即j-1
理解了为什么,接下来讲讲怎么做。
当我们要计算mx[i]时,其实value[1~i]都已经在深搜的过程中枚举好了,那我们将求mx[i]的问题再进行简化。
求已知的若干个数,第i个数的值为a[i],求构成数值为m的数,最少需要多少个数?
比较经典的dp吧 , 转移方程为dp[j] = min(dp[j], dp[j-a[i]]+1)(这是一维的,降维后是这个转移方程)
mx也是一样的,我们先通过动态规划求出前i种邮票能构成面值为j的最少数量,之后从小到大枚举面值,遇到所需数量大于n的面值就break,跳出时的面值-1即为mx


如果看了上面还是没思路,但是还想自己写代码的同学,可以看看下面提示的伪代码。

本题的伪代码:
请添加图片描述

代码

#include<bits/stdc++.h>
using namespace std;
const int N = 13*13;

int n, k, ans, res[N], value[N], f[N];

int dp(int x) {
	memset(f, 0x3f, sizeof f); // 动规初始化 
	f[0] = 0;
	
	for(int i = 1;i <= x;i ++)
	for(int j = value[i];j <= n*value[x];j ++) // j最大为 n*value[x],即n张全选第x种邮票 
	f[j] = min(f[j], f[j-value[i]]+1);
	
	int i;
	for(i = 1;i <= n*value[x];i ++) if(f[i] > n) break;
	return i-1;
}

void dfs(int x, int mx) {
	if(x > k) {
		if(mx > ans) {
			ans = mx;
			for(int i = 1;i <= k;i ++) res[i] = value[i];
		}
		return ;
	}
	for(int i = value[x-1]+1;i <= mx+1;i ++) { // 枚举第x个邮票的面值范围 
		value[x] = i; 
		dfs(x+1, dp(x));
	}
}


int main()
{
	cin>>n>>k;
	
	dfs(1, 0); // 第一个参数是现在要确定第几个邮票的面值,第二个参数是前面的邮票构成的最大连续数是多少 

	for(int i = 1;i <= k;i ++) cout << res[i] << ' ';
	cout << endl << "MAX=" << ans;
	return 0;
}

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不牌不改

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值