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]);
}
}