POJ 3181 Dollar Dayz 完全背包 高精度dp 大数

本文探讨了一道经典的完全背包问题,通过动态规划解决工具购买组合问题,并深入讲解了如何使用大数处理避免整数溢出,提供两种实现方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description
Farmer John goes to Dollar Days at The Cow Store and discovers an unlimited number of tools on sale. During his first visit, the tools are selling variously for $1, $2, and $3. Farmer John has exactly $5 to spend. He can buy 5 tools at $1 each or 1 tool at $3 and an additional 1 tool at $2. Of course, there are other combinations for a total of 5 different ways FJ can spend all his money on tools. Here they are:
1 @ US$3 + 1 @ US$2
1 @ US$3 + 2 @ US$1
1 @ US$2 + 3 @ US$1
2 @ US$2 + 1 @ US$1
5 @ US$1
Write a program than will compute the number of ways FJ can spend N dollars (1 <= N <= 1000) at The Cow Store for tools on sale with a cost of 1..1..1..K (1 <= K <= 100).
Input
A single line with two space-separated integers: N and K.
Output
A single line with a single integer that is the number of unique ways FJ can spend his money.
Sample Input
5 3
Sample Output
5

题目大意:
给定k种硬币,每种硬币价值分别为1,2,3…k,每种硬币的数量不限,求出使用这k种硬币构成n元的方法

解题思路:
一看完题,瞬间想到了完全背包问题,然后想到了数组降维,动态规划方程式为

for (int i = 1; i <= k; i++) {//对每种硬币进行考虑
	//每一轮开始前,dp[j]是指只使用前i-1种硬币组成价值j的方法数
	for (int j = i; j <= n; j++) {
		dp[j] = dp[j] + dp[j - i];//dp[j-i]在这里是指用上了第i种硬币(至少一枚)
	}
	//每一轮结束后,dp[j]是指只使用前i种硬币组成价值j的方法数
}

得到代码为:

#include<iostream>
using namespace std;
int n, k;
int dp[1001];
int main() {
	while (cin >> n >> k) {
		dp[0] = 1;
		for (int i = 1; i <= 1001; i++) dp[i] = 0;
		for (int i = 1; i <= k; i++) {
			for (int j = i; j <=n; j++) {
				dp[j] += dp[j - i];
			}
		}
		//for (int i = 1; i <= n; i++) cout << dp[i] << endl;
		cout << dp[n] << endl;
	}
}

心情愉悦前去提交,然后得到了一发WA (ΩДΩ)
看了讨论区之后发现是要使用大数防止溢出,然后我又回去参考了书上关于高精度整数的那部分介绍,得到如下AC代码:
AC代码1

#include<iostream>
#include<stdio.h>
using namespace std;
int n, k;
struct bigInt {
	int digit[1001];
	int size;
	void init() {//初始化
		for (int i = 0; i <= 1000; i++) {
			digit[i] = 0;
		}
		size = 0;
	}
	void set(int x) {//用一个小整数设置高精度整数
		init();//初始化
		do {
			digit[size++] = x % 10000;
			x /= 10000;
		} while (x != 0);
	}
	void output() {//输出函数
		for (int i = size - 1; i >= 0; i--) {
			if (i == size - 1) { printf("%d", digit[i]); }
			else printf("%04d", digit[i]);
		}
		printf("\n");
	}
	bigInt operator + (const bigInt & A) const {
		bigInt ret;//保存结果
		ret.init();
		int carry = 0;//进位
		for (int i = 0; i < A.size || i < size; i++) {
			int tmp = A.digit[i] + digit[i] + carry;
			carry = tmp / 10000;
			tmp %= 10000;
			ret.digit[ret.size++] = tmp;
		}
		if (carry != 0) {
			ret.digit[ret.size++] = carry;
		}
		return ret;
	}
}dp[1001];//dp[i][j]从前i种价格中花j元
int main() {
	while (scanf("%d%d",&n,&k)!=EOF) {
		dp[0].set(1);
		for (int i = 1; i <= 1001; i++) dp[i].set(0);
		for (int i = 1; i <= k; i++) {
			for (int j = i; j <= n; j++) {
				dp[j] = dp[j] + dp[j - i];
			}
		}
		dp[n].output();
	}
}

写起来较为臃肿,且用时300+ms,然后我又看了一下大佬们的用时,基本都在50ms以内,看了一些大佬们的思路,发现可以用两个18位整数构成这个大数,我觉得挺不错的,嘿嘿
AC代码2

#include<iostream>
#include<stdio.h>
using namespace std;
int n, k;
long long dp1[1001];//高18位
long long dp2[1001];//低18位
const long long M = 1e18;
int main() {
	while (scanf("%d%d", &n, &k) != EOF) {
		dp2[0] = 1; dp1[0] = 0;
		for (int i = 1; i <= n; i++) {
			dp2[i] = 0; dp1[i] = 0;
		}
		long long carry = 0;
		for (int i = 1; i <= k; i++) {
			for (int j = i; j <= n; j++) {
				carry = (dp2[j] + dp2[j - i]) / M;
				dp1[j] = dp1[j] + dp1[j - i] + carry;
				dp2[j] = (dp2[j] + dp2[j - i]) % M;
			}
		}
		if (dp1[n] > 0) {
			printf("%lld%018lld\n", dp1[n], dp2[n]);
		}
		else printf("%lld\n", dp2[n]);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值