经典DP之硬币局

经典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;
} 

好啦~今天就到这里啦,如果还有相关题型偶会继续补的啦

发布了53 篇原创文章 · 获赞 2 · 访问量 1366

猜你喜欢

转载自blog.csdn.net/qq_44624316/article/details/104359177
今日推荐