基础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.只能选一次问有多少种组合
组合数优先考虑物品种类,有限次就从最后开始遍历,转移时直接+
#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次
小明的花店新开张,为了吸引顾客,他想在花店的门口摆上一排花,共 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;
}

                
            
        
浙公网安备 33010602011771号