纸币问题:动规例题(详解)

前言

本蒟蒻今天在洛谷上练动态规划,遂写此篇

在这里插入图片描述

一、纸币问题 1

P2842 纸币问题 1

题目描述

某国有 \(n\) 种纸币,每种纸币面额为 \(a_i\) 并且有无限张,现在要凑出 \(w\) 的金额,试问最少用多少张纸币可以凑出来?

输入格式

第一行两个整数 \(n,w\),分别表示纸币的种数和要凑出的金额。
第二行一行 \(n\) 个以空格隔开的整数 \(a_1, a_2, a_3, \dots a_n\) 依次表示这 \(n\) 种纸币的面额。

输出格式

一行一个整数,表示最少使用的纸币张数。

提示

对于 \(40\%\) 的数据,满足 \(n\le 10\)\(w\le 100\)
对于 \(100\%\) 的数据,满足 \(1\le n\le 10^3\)\(1\le a_i \leq w\le 10^4\)

题解

此题是简单的动态规划,求能构成给定数值的纸币数量的最小值。
\(dp[j]\) 表示前 \(i\) 种面额的纸币构成数值为 \(j\) 的金额所需要的最少纸币数量,初状态赋值为\(-1\),表示数值为 \(j\) 的金额无法得到。
对于当前循环的 \(i,j\),如果 \(dp[j-a[i]]!=0\) ,那么则要考虑由数值为 \(j-a[i]\) 的金额得到数值为 \(j\) 的金额能否更新其最小值。
整个过程只需要注意判定 \(dp[j]\) 是否为 \(-1\) 即可。

代码如下

#include<bits/stdc++.h>
using namespace std;
int n,w,a[1001],dp[10001];
int main()
{
	memset(dp,-1,sizeof(dp));
	cin>>n>>w;
	for(int i=1;i<=n;i++) cin>>a[i];
	dp[0]=0;
	for(int i=1;i<=n;i++)
	for(int j=a[i];j<=w;j++)
	if(dp[j-a[i]]!=0)
	if(dp[j]==-1) dp[j]=dp[j-a[i]]+1;
	else dp[j]=min(dp[j],dp[j-a[i]]+1);
	cout<<dp[w]<<endl;
	return 0;
}

二、纸币问题 2

P2840 纸币问题 2

题目背景

你是一个非常有钱的小朋友。

题目描述

你有 \(n\) 种面额互不相同的纸币,第 \(i\) 种纸币的面额为 \(a_i\) 并且有无限张,现在你需要支付 \(w\) 的金额,求问有多少种方式可以支付面额 \(w\),答案对 \(10^9+7\) 取模。
注意在这里,同样的纸币组合如果支付顺序不同,会被视作不同的方式。例如支付 \(3\) 元,使用一张面值 \(1\) 的纸币和一张面值 \(2\) 的纸币会产生两种方式(\(1+2\)\(2+1\))。

输入格式

第一行两个正整数 \(n,w\),分别表示纸币的种数和要凑出的金额。
第二行一行 \(n\) 个以空格隔开的正整数 \(a_1, a_2, \dots a_n\) 依次表示这 \(n\) 种纸币的面额。

输出格式

一行一个整数,表示支付方式的数量。

提示

对于 \(40\%\) 的数据,满足 \(n\le 10\)\(w\le 100\)
对于 \(100\%\) 的数据,满足 \(1\le n\le 10^3\)\(1\le a_i \le w\le 10^4\)

其实小朋友并不有钱。

题解

这题困扰了我很久,关键在于支付顺序调换会导致不同支付方式的产生,那么如何 \(dp\) 呢?
先给出状态转移方程

\[dp[i]=\sum_{j=1}^{n}{dp[i-a[j]]}(i>=a[j]) \]

外层循环 \(i=1\dots w\),内层循环 \(j=1\dots n\)
这样是如何将顺序调换考虑在内的呢?
我们可以 \(a[j]\) 理解为“最后一个支付的纸币” ,下面来模拟看看:

输入如下:

\(n\) \(w\) \(a[0]\) \(a[1]\) \(a[2]\) \(a[3]\) \(a[4]\) \(a[5]\) \(a[6]\)
\(6\) \(15\) \(0\) \(1\) \(5\) \(10\) \(20\) \(50\) \(100\)

模拟如下:

$$i$$ $$0$$ $$1$$ $$2$$ $$3$$ $$4$$ $$5$$ $$6$$
$$dp[i]$$ \(1\) \(1\) \(1\) \(1\) \(1\) \(2\) \(3\)
\(i\) 方式1 方式2 方式3
0 \(0\) -- --
1 \(1\)(最后是1) -- --
2 \(1+1\)(最后是1) -- --
3 \(1+1+1\)(最后是1) -- --
4 \(1+1+1+1\)(最后是1) -- --
5 \(1+1+1+1+1\)(最后是1) \(5\) (最后是5) --
6 \(1+1+1+1+1+1\)(最后是1) \(5+1\)(最后是1) \(1+5\)(最后是5)
\(\dots\) \(\dots\) \(\dots\) \(\dots\)

说明:\(dp[0]=1\) 表示支付 \(0\) 元仅一种方式,即不付钱

\(dp[0\dots i-1]\) 已经求得所有情况时,仅需考虑求 \(dp[i]\) 的最后一张纸币的面额 \(a[j]\) 即可,因为在计算每一个 \(dp[i]\) 时,已经将所有纸币面额 \(a[j]\) 考虑在内;确定最后一张的面额并进行情况数加和的过程中,已经包含了计算排列的操作。换句话说,\(dp[i-a[j]]\) 已经将所有排列考虑好了,只需要把最后的 \(a[j]\) 加上去,就得到了 \(dp[i]\) 的一种新情况,并且最后不会有漏算的或重复的(如表中 \(i=6\) 时,可以清晰地看到三种情况)。这很好地体现了“动态规划”的意义。
更形象地来说,考虑将 \(6\) 拆成 \(1+5\) 或者 \(5+1\),当最后一个加的是 \(5\)时,前面的包括 \(1\) 在内的所有数已经排列过了,也就是说 \(1\) 已经出现在了除了最后一个位置之外的所有可能位置,那么只需要再加上 \(1\) 在最后位置的情况,\(1\) 的排列就没有遗漏了。

另外注意:好事多%

代码如下

#include<bits/stdc++.h>
using namespace std;
int n,w,a[1001],dp[10001];
const int mod=1e9+7;
int main()
{
    cin>>n>>w;
    for(int i=1;i<=n;i++) cin>>a[i];
    dp[0]=1;
    for(int i=1;i<=w;i++)
    for(int j=1;j<=n;j++)
	{
        if(i-a[j]>=0)
		dp[i]=(dp[i]+dp[i-a[j]])%mod;
    }
    cout<<(dp[w]%mod)<<endl;
    return 0;
}

三、纸币问题 3

P2834 纸币问题 3

题目背景

你是一个非常有钱的小朋友。

注意:本题和《进阶篇》的对应题目,输入格式略有差异。

题目描述

你有 \(n\) 种面额互不相同的纸币,第 \(i\) 种纸币的面额为 \(a_i\) 并且有无限张,现在你需要支付 \(w\) 的金额,请问有多少种纸币组合能恰好支付金额 \(w\),答案对 \(10^9+7\) 取模。

输入格式

第一行两个正整数 \(n,w\),分别表示纸币的种数和要凑出的金额。
第二行一行 \(n\) 个以空格隔开的正整数 \(a_1, a_2, \dots a_n\) 依次表示这 \(n\) 种纸币的面额。

输出格式

一行一个整数,表示能恰好凑齐面额 \(w\) 的纸币组合数量。

提示

对于 \(40\%\) 的数据,满足 \(n\le 10\)\(w\le 100\)
对于 \(100\%\) 的数据,满足 \(1\le n\le 10^3\)\(1\le a_i \le w\le 10^4\)

其实小朋友并不有钱。

题解

本题是完全背包问题,可以直接用模板,\(dp[j]\) 记录前 \(i\) 种面额的纸币组成数值为 \(j\) 的钱的方案数,每次循环增加一种纸币面额,依次更新 \(dp[1\dots w]\) 即可。(注意取模)

代码如下

#include<bits/stdc++.h>
using namespace std;
int n,w,a[1001],dp[10001];
const int mod=1e9+7;
int main()
{
    cin>>n>>w;
    for(int i=1;i<=n;i++) cin>>a[i];
    dp[0]=1;
    for(int i=1;i<=n;i++)
    for(int j=a[i];j<=w;j++)
    {
    	dp[j]+=dp[j-a[i]]%mod;
		dp[j]%=mod;	
	}
    cout<<(dp[w]%mod)<<endl;
    return 0;
}

四、问题2与问题3的区别

纸币问题2实际上是方案数排列问题,纸币问题3仅仅是方案数组合问题。因此在动态规划上内外层循环略有不同,二者在递推过程中都符合题目要求完成了计算。

( ^ _ ^ ) /

posted @   Gcint  阅读(69)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示