基础DP

引入

P2842 纸币问题 1

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

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

#include<bits/stdc++.h>
using namespace std;
int inf=1e9;
int n,w;
int a[10010];
int dp[10010];
int main(){
	cin>>n>>w;
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=1;i<=w;i++)dp[i]=inf;
	dp[0]=0;
	for(int i=1;i<=w;i++){
		for(int j=1;j<=n;j++){
			if(i>=a[j])dp[i]=min(dp[i],dp[i-a[j]]+1);
		}
	}
	cout<<dp[w];
	return 0;
}

对每个w枚举所有种纸币来转移状态

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\) 种纸币的面额。

#include<bits/stdc++.h>
using namespace std;
int mod=1e9+7;
int n,w;
int a[10010];
long long dp[10010];
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])dp[i]=(dp[i]+dp[i-a[j]])%mod;
		}
	}
	cout<<dp[w]%mod;
	return 0;
}

考虑纸币顺序不同的情况,依旧对每个w枚举所有种纸币来转移状态,方案数+=用这种纸币时的方案数,此便能算出每一种顺序

P2834 纸币问题 3

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

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

#include<bits/stdc++.h>
using namespace std;
int mod=1e9+7;
int n,w;
int a[10010];
long long dp[10010];
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]+dp[j-a[i]])%mod;
	}
	cout<<dp[w]%mod;
	return 0;
}

考虑到题目要的是组合,转变思路先考虑纸币种类在考虑金额,便不会造成顺序不一的情况

背包问题

0-1背包

P1048 [NOIP 2005 普及组] 采药

题目描述

辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”

如果你是辰辰,你能完成这个任务吗?

第一行有 \(2\) 个整数 \(T\)\(1 \le T \le 1000\))和 \(M\)\(1 \le M \le 100\)),用一个空格隔开,\(T\) 代表总共能够用来采药的时间,\(M\) 代表山洞里的草药的数目。

接下来的 \(M\) 行每行包括两个在 \(1\)\(100\) 之间(包括 \(1\)\(100\))的整数,分别表示采摘某株草药的时间和这株草药的价值。

像这样背包有限,每种物品只能去一次的问题为0-1背包

因为还是优先考虑每一种药再通过时间转移

并且每种药只能取一次,采用纸币问题3的做法会采很多次,因此反过来遍历时间便可使每一种药只采一次

#include<bits/stdc++.h>
using namespace std;
int t,n;
int a[110],b[110];
int dp[1010];
int main(){
	cin>>t>>n;
	for(int i=1;i<=n;i++)cin>>a[i]>>b[i];
	for(int i=1;i<=n;i++){
		for(int j=t;j>=a[i];j--){
			dp[j]=max(dp[j],dp[j-a[i]]+b[i]);
		}
	}
	cout<<dp[t];
	return 0;
}

完全背包

P1616 疯狂的采药

与0-1背包相反,每种物品都可以用无数次

那么只要正向遍历时间即可

#include<bits/stdc++.h>
using namespace std;
int t,n;
int a[11000],b[11000];
long long dp[20000010];
int main(){
	cin>>t>>n;
	for(int i=1;i<=n;i++)cin>>a[i]>>b[i];
	for(int i=1;i<=n;i++){
		for(int j=a[i];j<=t;j++){
			dp[j]=max(dp[j],dp[j-a[i]]+b[i]);
		}
	}
	cout<<dp[t];
	return 0;
}

背包的几种变式

1.只能选一次问有多少种组合

P1164 小A点菜

组合数优先考虑物品种类,有限次就从最后开始遍历,转移时直接+

#include<bits/stdc++.h>
using namespace std;
int n,m;
int a[110];
int dp[10010];
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++)cin>>a[i];
	dp[0]=1;
	for(int i=1;i<=n;i++){
		for(int j=m;j>=a[i];j--){
			dp[j]+=dp[j-a[i]];
		}
	}
	cout<<dp[m];
	return 0;
}

2.第i个物品选取不超过ai次

P1077 [NOIP 2012 普及组] 摆花

小明的花店新开张,为了吸引顾客,他想在花店的门口摆上一排花,共 m 盆。通过调查顾客的喜好,小明列出了顾客最喜欢的 n 种花,从 1 到 n 标号。为了在门口展出更多种花,规定第 i 种花不能超过 a i 盆,摆花时同一种花放在一起,且不同种类的花需按标号的从小到大的顺序依次摆列。

试编程计算,一共有多少种不同的摆花方案。

#include<bits/stdc++.h>
using namespace std;
int mod=1000007;
int n,m;
int a[110];
int dp[110];
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++)cin>>a[i];
	dp[0]=1;
	for(int i=1;i<=n;i++){
		for(int j=m;j>=1;j--){
			for(int k=1;k<=min(j,a[i]);k++)dp[j]=(dp[j]+dp[j-k])%mod;//枚举摆花的盆数即可
		}
	}
	cout<<dp[m]%mod;
	return 0;
}
posted @ 2025-09-16 17:33  R-99Player  阅读(27)  评论(0)    收藏  举报