经典DP之硬币局
罗列从易到难的经典硬币DP
嘻嘻,昨天晚上一不小心把书翻了翻,看到自己不熟练的部分,昨天下定决心要做一个全面的总结,于是乎~今天就来啦,嘻嘻,不得不说,这本书是真的买值了呀,我可不能辜负它啦,其实下面的题目大多数算法课高老师都讲过,只不过我那时候就是听听,敲了敲代码,没有进行总结,于是很容易忘,也没有形成总体的知识体系,做题不能立刻get到是什么方向=-=今早上完课就做了几道cf练手,就来总结啦,嘻嘻哈哈
1.组成最少硬币个数
dp[i]对应金额i的最少硬币数量
这里假设硬币面值有以下几种:type[] = {1, 5, 10, 25, 50}
一重循环跑一遍硬币面值,一重循环跑一遍所求总数
初始化:dp[0] = 0,dp[i] = INF(i > 0)
状态转移方程:dp[i] = min(dp[i], dp[i - type[j]] + 1)
嘻嘻,下面是代码部分:
#include <bits/stdc++.h>
using namespace std;
const int MONEY = 251;
const int VALUE = 5;
int type[VALUE] = {1, 5, 10, 25, 50};
int dp[MONEY];
void solve()
{
for (int k = 0; k < MONEY; k++)
{
dp[k] = INT_MAX;
}
dp[0] = 0;
for (int j = 0; j < VALUE; j++)
{
for (int i = type[j]; i < MONEY; i++)
{
dp[i] = min(dp[i], dp[i - type[j]] + 1);
}
}
}
int main()
{
int s;
solve();
while (cin >> s)
{
cout << dp[s] << endl;
}
return 0;
}
首先打表即可
时间复杂度:O(VALUE * MONEY)
2.打印最少硬币组合
输出最优解本身,我们就可以增加一个记录表啦Min_path[]
数组记录当前金额的最少硬币组合的最后一个硬币的面值即可,然后在数组基础上倒推,就可以得到这个完整的组合啦~
比如当前金额为7,则该组合为5 + 1 + 1,那么我们在Min_path[7]中存5,即Min_path[7] = 5,然后7 - 5 = 2,再去找2的最少硬币组合直到0就可以结束啦~
这是记录路径的常见方法~
我们记录路径的时候在维护最小的组合是更新就好啦~
上代码啦~
#include <bits/stdc++.h>
using namespace std;
const int MONEY = 251;//定义最大金额
const int VALUE = 5;//5种硬币
int type[VALUE] = {1, 5, 10, 25, 50};//五种面值
int dp[MONEY];//每种金额对应的最少硬币数量
int Min_path[MONEY];//记录最小硬币的路径
void solve()
{
for (int k = 0; k < MONEY; k++)
{
dp[k] = INT_MAX;
}
dp[0] = 0;
for (int j = 0; j < VALUE; j++)
{
for (int i = type[j]; i < MONEY; i++)
{
if (dp[i] > dp[i - type[j]] + 1)
{
Min_path[i] = type[j];
dp[i] = dp[i - type[j]] + 1;
}
}
}
}
void print_ans(int s)
{
while (s)
{
cout << Min_path[s] << " ";
s -= Min_path[s];
}
cout << endl;
}
int main()
{
int s;
solve();
while (cin >> s)
{
cout << dp[s] << endl;
print_ans(s);
}
return 0;
}
这个代码就是在上面的代码基础上增加了一些内容哦~
3.所有可能的硬币组合数量
这里输出所有可能的硬币组合数量跟上面的第一种情况大同小异啦~(可以说是一模一样,就是定义出来的dp的含义不一样而已=-=)
dp[i]代表组成金额为
dp[i]代表组成金额为i的所有组合方案数
初始化:dp[0] = 1, dp[i] = 0(i > 0)
状态转移方程:dp[i] = dp[i] + dp[i - type[j]]
代码部分~:
#include <bits/stdc++.h>
using namespace std;
const int MONEY = 251;
const int VALUE = 5;
int type[VALUE] = {1, 5, 10, 25, 50};
int dp[MONEY];
void solve()
{
dp[0] = 1;
for (int j = 0; j < VALUE; j++)
{
for (int i = type[j]; i < MONEY; i++)
{
dp[i] = dp[i] + dp[i - type[j]];
}
}
}
int main()
{
int s;
solve();
while (cin >> s)
{
cout << dp[s] << endl;
}
return 0;
}
同样也是打表~
然后呢,这个时间复杂度也是和情况1是一样的啦~
时间复杂度:O(VALUE * MONEY)
4.限制了硬币数量的所有可能的硬币组合数量
情况三简单到硬币数量都没有限制=-=
那么情况四就需要考虑一下数量限制的情况啦~很显然,一维dp已经不能满足我们的需求了,我们需要二维啦
这里参考hdu的一道题~
题目限制了硬币总数在100个范围之内啦~
dp[i][j]代表用 j 个硬币实现金额 i 的方案数量
初始化:dp[0][0] = 1
状态转移方程:dp[i][j] += dp[i - type[k]][j - 1]
从金额为 i 面值中减去type[k]的面值,原来的硬币数量也需要减去1
题目要我们输出金额的方案总数,我们最后求一下和就可以啦~ans[]保存方案数
代码在下面啦~
#include <bits/stdc++.h>
using namespace std;
const int COIN = 101;
const int MONEY = 251;
int dp[MONEY][COIN];
int type[5] = {1, 5, 10, 25, 50};
void solve()
{
dp[0][0] = 1;
for (int i = 0; i < 5; i++)
{
for (int j = 1; j < COIN; j++)
{
for (int k = type[i]; k < MONEY; k++)
{
dp[k][j] += dp[k - type[i]][j - 1];
}
}
}
}
int main()
{
int s;
int ans[MONEY] = {0};
solve();
for (int i = 0; i < MONEY; i++)
{
for (int j = 0; j < COIN; j++)
{
ans[i] += dp[i][j];
}
}
while (cin >> s)
{
cout << ans[s] << endl;
}
return 0;
}
好啦~今天就到这里啦,如果还有相关题型偶会继续补的啦