邮票问题详细题解【简单dp】

已知一个 N 枚邮票的面值集合(如,{1 分,3 分})和一个上限 K —— 表示信封上能够贴 K 张邮票。计算从 1 到 M 的最大连续可贴出的邮资。

例如,假设有 1 分和 3 分的邮票;你最多可以贴 5 张邮票。很容易贴出 1 到 5 分的邮资(用 1 分邮票贴就行了),接下来的邮资也不难:

6 = 3 + 3
7 = 3 + 3 + 1
8 = 3 + 3 + 1 + 1
9 = 3 + 3 + 3
10 = 3 + 3 + 3 + 1
11 = 3 + 3 + 3 + 1 + 1
12 = 3 + 3 + 3 + 3
13 = 3 + 3 + 3 + 3 + 1

然而,使用 5 枚 1 分或者 3 分的邮票根本不可能贴出 14 分的邮资。因此,对于这两种邮票的集合和上限 K=5,答案是 M=13。


我们先给出将其看为多重集组合数来解得场景,不同之处在于,这里限制了邮票使用的最大数目。我们使用两个数组

d p [ i ] , c n t [ i ] dp[i],cnt[i] dp[i],cnt[i]表示使用最少 c n t [ i ] cnt[i] cnt[i]个邮票就能拼凑出大小为 i i i的邮资,那么动态规划的过程分为三步

  • 遍历每一个邮票
  • 遍历从当前拼凑出的最大的邮资开始到0为止的所有邮资的状况, m a x j maxj maxj可以看作是一步剪枝,避免了一直从可能的最大值向前遍历。
  • 因为提前记录了每个 j j j需要的最小的邮票数目,因此我们限制使用当前邮票的总数在 K − c n t [ j ] K-cnt[j] Kcnt[j]之内,同时遇到已经拼凑出的值之后,如果可以用更少的邮票数目,那么更新。
		for (int i = 1; i <= N; i++)cin >> a[i];
		sort(a + 1, a + N); dp[0] = 1;
		ll sum = K * a[N], maxj = 0;//最多能凑成的邮资
		for (int i = 1; i <= N; i++) {//遍历每个邮票
			for (int j = maxj; j >= 0; j--) {
				if (!dp[j])continue;//j没有拼凑出来
				for (int k = 1; k <= K - cnt[j]; k++) {//保证不会超过总数
					if (dp[j + k * a[i]]) {
						if (cnt[j + k * a[i]] > cnt[j] + k)
							cnt[j + k * a[i]] = cnt[j] + k;//用最少的邮票拼凑出来
					}
					else dp[j + k * a[i]] = 1, cnt[j + k * a[i]] = cnt[j] + k;
				}
			}
			maxj = K * a[i];
		}

不幸的是,多重集组合数经常面临着TLE的危险,上面也不例外,只能拿80分,看了大佬的题解之后发现自己是一个zz,其实只需要很简单得转换,我们就能拿下这道题,

注意看题,题目求得是从1开始得最长连续序列,因此我们只需要从i=0不断向上搜索,直到有一个i不能拼凑出来,就得到了最长连续序列

最重要的部分只有4行, d p [ i ] dp[i] dp[i]是我们拼凑出 i i i邮资使用的最少邮票数目,dp得过程其实就是不断更新最小邮票数目得过程,在满足数目要求 d p [ i ] < = K dp[i]<=K dp[i]<=K时,我们先将 d p [ i + 1 ] dp[i+1] dp[i+1]设置为inf,然后遍历每一种邮票,确保邮票得价值小于当前得 i i i(注意这里要保证a从小到大排列),然后再满足上述条件得情况下,使得 d p [ i ] dp[i] dp[i]总是最小得

		while (dp[i] <= K) {
			i++; dp[i] = inf;
			for (int j = 1; j <= N && a[j] <= i; j++)
				dp[i] = min(dp[i], dp[i - a[j]] + 1);
		}
#define ll int
#define inf 0x3f3f3f3f
#define MAX 55
#define vec vector<ll>
#define P pair<ll,ll>

//dp[j]:凑出j需要的最小邮票数目
int K, N, a[MAX], dp[2000005];

int main() {
	
	while (cin >> K >> N) {
		memset(dp, 0, sizeof(dp));
		ll maxx = 0;
		for (int i = 1; i <= N; i++)cin >> a[i];
		sort(a + 1, a + N + 1);//一定要排序
		int i = 0;
		while (dp[i] <= K) {
			i++; dp[i] = inf;
			for (int j = 1; j <= N && a[j] <= i; j++)
				dp[i] = min(dp[i], dp[i - a[j]] + 1);
		}
		cout << i - 1 << endl;
	}
}
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值