dp的俩个性质
无后效性
最优子结构
dp的俩大要素
状态
状态转移方程
基础DP
支付问题
#include <iostream>
#include <climits>
using namespace std;
//dp支付问题
/*
* 15
* 1 5 11
* 按贪心算法来做的话,11*1+1*4-->5个
* 正常来算的话,5*3-->3个
* 最终答案应该为3,因为贪心具有后效性
* 用dp来做
* */
const int N=1e4+10;
int dp[N];
//状态:dp[i]-->支付i元所需最小的硬币数量
//状态转移方程:dp[i]=min{dp[i-11],dp[i-4],dp[i-1]}+1
int main(){
int w;cin>>w;
for(int i=1;i<=w;i++){
int mi=INT_MAX;
if(i>=1) mi=min(mi,dp[i-1]);
if(i>=5) mi=min(mi,dp[i-5]);
if(i>=11) mi=min(mi,dp[i-11]);
dp[i]=mi+1;
//print dp table
cout<<"dp["<<i<<"]="<<dp[i]<<endl;
}
cout<<dp[w]<<endl;
return 0;
}
二维格子
#include <iostream>
#include <vector>
#include <climits>
using namespace std;
const int N=1e3+10;
int a[N][N],dp[N][N];
//状态:dp[i][j]表示从起点(1,1)到终点(i,j)所消耗的最小体力
//状态转移方程:dp[i][j]=min(dp[i-1][j],dp[i][j-1])+a[i][j]
int main(){
int n;cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>a[i][j];
//边界处理:
//第一行
for(int j=1;j<=n;j++) dp[1][j]=dp[1][j-1]+a[1][j];
//第一列
for(int i=1;i<=n;i++) dp[i][1]=dp[i-1][1]+a[i][1];
for(int i=2;i<=n;i++){
for(int j=2;j<=n;j++){
dp[i][j]=min(dp[i-1][j],dp[i][j-1])+a[i][j];
}
}
//print_dp_Table
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cout<<dp[i][j]<<"\t";
}cout<<endl;
}
cout<<dp[n][n]<<endl;
return 0;
}
三角形最佳路径问题
#include <iostream>
#include <vector>
#include <climits>
using namespace std;
const int N=1e3+10;
int a[N][N],dp[N][N];
//状态:dp[i][j]-->(1,1)点到(i,j)点的最大路径和
//状态转移方程:dp[i][j]=max(dp[i-1][j],dp[i-1][j-1])+a[i][j]
int main(){
int n;cin>>n;
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
cin>>a[i][j];
}
}
// for(int i=1;i<=n;i++){
// for(int j=1;j<=i;j++){
// cout<<a[i][j]<<" ";
// }cout<<endl;
// }
//第一列和对角线:
dp[1][1]=a[1][1];
for(int i=2;i<=n;i++){
dp[i][1]=dp[i-1][1]+a[i][1];
dp[i][i]=dp[i-1][i-1]+a[i][i];
}
for(int i=3;i<=n;i++){
for(int j=2;j<i;j++){
dp[i][j]=max(dp[i-1][j],dp[i-1][j-1])+a[i][j];
}
}
// for(int i=1;i<=n;i++){
// for(int j=1;j<=i;j++){
// cout<<dp[i][j]<<" ";
// }cout<<endl;
// }
int mx=INT_MIN;
for(int j=1;j<=n;j++){
mx=max(mx,dp[n][j]);
}
cout<<mx<<endl;
return 0;
}
单序列DP
最大子段和(连续子段)
#include <iostream>
#include <climits>
using namespace std;
const int N=2e5+10;
int a[N],dp[N];
//状态:dp[i]-->以第i个元素为结尾的最大子段和
//状态转移方程:dp[i]=max(dp[i-1]+a[i],a[i])
int main(){
int n,mx=INT_MIN;cin>>n;
//-1 2 3 -4 1 3
//-1 2 5
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
//第i个状态由第i-1个状态加一些决策得出
dp[i]=max(dp[i-1]+a[i],a[i]);
mx=max(mx,dp[i]);
}
cout<<mx<<endl;
return 0;
}
压维的序列DP(最大子矩阵)
#include <iostream>
#include <climits>
using namespace std;
const int N=1e2+10;
int a[N][N],dp[N],b[N],presum[N][N];
//状态:dp[j]-->压维后以j结尾元素的最大字段和也就是最大子矩阵和
//状态转移方程:dp[j]=max(dp[j-1]+a[j],a[j])
int main(){
//处理前缀和
int n;cin>>n;
for(int i=1;i<=n;i++) {
for (int j = 1; j <= n; j++) {
cin >> a[i][j];
//固定j列,第j列的前缀和
presum[i][j] = presum[i - 1][j] + a[i][j];
}
}
int mx=INT_MIN;
for(int i1=1;i1<=n;i1++){
for(int i2=i1;i2<=n;i2++){//枚举i1 i2
//压维
for(int j=1;j<=n;j++){
b[j]=presum[i2][j]-presum[i1-1][j];
}
for(int j=1;j<=n;j++){
dp[j]=max(dp[j-1]+b[j],b[j]);
mx=max(mx,dp[j]);
}
}
}
cout<<mx<<endl;
return 0;
}
最长上升子序列(LIS)
Longest Increase Subsequence
#include <iostream>
#include <climits>
using namespace std;
const int N=1e3+10;
int a[N],pre[N],n,LIS[N];
//dp_LIS[i]-->以i结尾的最长上升子序列长度
int main(){
/* 1 2 3 4 5 6 7
* a 1 7 3 5 9 4 8
* dp 1 2 2 3 4 3 4
* */
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
LIS[i]=1;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=i-1;j++){
if(a[j]<a[i]) LIS[i]=max(LIS[i],LIS[j]+1);
}
}
int mx=INT_MIN;
for(int i=1;i<=n;i++){
mx=max(mx,LIS[i]);
}
cout<<mx<<endl;
return 0;
}
求最长不下降序列
#include <iostream>
#include <climits>
using namespace std;
const int N=1e3+10;
int a[N],pre[N],n,LIS[N];
//dp_LIS[i]-->以i结尾的最长上升子序列长度
void print(int s){
if(s==0) return;
print(pre[s]);
cout<<a[s]<<" ";
}
int main(){
/* 1 2 3 4 5 6 7
* a 1 7 3 5 9 4 8
* dp 1 2 2 3 4 3 4
* 1 3 6 7
* 1 3 4 8
* */
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
LIS[i]=1;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=i-1;j++){
if(a[j]<=a[i]&&LIS[i]<LIS[j]+1){
LIS[i]=LIS[j]+1;
pre[i]=j;
}
}
}
int mx=INT_MIN,mxId=0;
for(int i=1;i<=n;i++){
if(mx<LIS[i]){
mx=LIS[i];
mxId=i;
}
}
cout<<"max="<<mx<<endl;
print(mxId);
return 0;
}
合唱队形
#include <iostream>
#include <climits>
using namespace std;
const int N=1e2+10;
int a[N],dp_up[N],dp_down[N];
int main(){
int n;cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
dp_up[i]=dp_down[i]=1;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
if(a[j]<a[i]){
dp_up[i]=max(dp_up[j]+1,dp_up[i]);
}
}
}
for(int i=n;i>=1;i--){
for(int j=i+1;j<=n;j++){
if(a[j]<a[i]){
dp_down[i]=max(dp_down[j]+1,dp_down[i]);
}
}
}
int mx=INT_MIN;
for(int i=1;i<=n;i++){
mx=max(mx,dp_down[i]+dp_up[i]-1);
}
cout<<n-mx<<endl;
return 0;
}
怪盗基德的滑翔翼
#include <iostream>
#include <climits>
#include <cstring>
using namespace std;
const int N=1e3+10;
int a[N],dp_down1[N],dp_down2[N],ans=0;
//dp[i]-->以第i元素结尾的最长下降子序列长度
//dp[i]=max(dp[i],dp[j]+1);
void solve(){
int n;cin>>n;
ans=0;
memset(dp_down1,0,sizeof dp_down1);
memset(dp_down2,0,sizeof dp_down2);
for(int i=1;i<=n;i++){
cin>>a[i];
dp_down1[i]=dp_down2[i]=1;
}
//以i为结尾的下降
for(int i=1;i<=n;i++){
for(int j=1;j<=i-1;j++){
if(a[j]<a[i]){
dp_down1[i]=max(dp_down1[i],dp_down1[j]+1);
}
}
}
//以i为结尾的下降
for(int i=n;i>=1;i--){
for(int j=i+1;j<=n;j++){
if(a[j]<a[i]){
dp_down2[i]=max(dp_down2[i],dp_down2[j]+1);
}
}
}
for(int i=1;i<=n;i++){
ans=max(ans,max(dp_down1[i],dp_down2[i]));
}
cout<<ans<<endl;
}
int main(){
int t;cin>>t;
while(t--){
solve();
}
return 0;
}
多序列DP
最长公共子序列(LCS)
Longest Common Subsequence
#include <iostream>
using namespace std;
/*
* dp[i][j]-->以s1的前i个字符和s2的前j个字符产生的最长公共子序列长度
* if(a[i]==a[j]) dp[i][j]=dp[i-1][j-1]+1;
* else dp[i][j]=max(dp[i-1][j],dp[i][j-1])
* i
* 1 2 3 4 5 6 7
* s1 A B C B D A B
* j
* 1 2 3 4 5 6
* s2 B D C A B A
* dp 1 2 3 4 5 6
* 1 0 0 0 1 1 1
* 2 1 1 1 1 2 2
* 3 1 1 2 2 2 2
* 4 1 1 2 2 3 3
* 5 1 2 2 2 3 3
* 6 1 2 2 3 3 4
* 7 1 2 2 3 4 4
* */
const int N=1e3+10;
int dp[N][N];
int main(){
string s1,s2;
cin>>s1>>s2;
int n=s1.size(),m=s2.size();
s1=' '+s1;s2=' '+s2;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(s1[i]==s2[j]) dp[i][j]=dp[i-1][j-1]+1;
else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
cout<<dp[n][m]<<endl;
return 0;
}
最短编辑距离
#include<iostream>
using namespace std;
/*
多序列dp-编辑距离Edit-Distance
1.状态 dp[i][j] s1的前i个字符变为s2的前j个字符所需的最小操作数(即最短编辑距离)
2.状态转移方程
if(s1[i]==s2[j]) dp[i][j]=dp[i-1][j-1]
else dp[i][j]=min{dp[i-1][j]删除,dp[i][j-1]添加, dp[i - 1][j - 1]直接修改} + 1消耗一次操作数
*/
/*
* i
* 1 2 3 4 5 6 7
* s1 s f d q x b w
* j
* 1 2 3 4 5
* s2 g f d g w
* dp 1 2 3 4 5
* 1
* 2
* 3
* 3
* 4
* 5
* 6
* 7
* */
// AB A
const int N = 2e3 + 10;
int dp[N][N];
int main() {
//边界 dp[i][0]=i dp[0][j]=j
string s1, s2;
cin >> s1 >> s2;
int n = s1.size(), m = s2.size();
s1 = ' ' + s1; s2 = ' ' + s2;//下标从1开始
//边界
for (int i = 1; i <= n; i++) dp[i][0] = i;
for (int j = 1; j <= m; j++) dp[0][j] = j;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (s1[i] == s2[j]) dp[i][j] = dp[i - 1][j - 1];
else dp[i][j] = min(min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;
// 删除 增加 x
}
}
cout << dp[n][m] << endl;
return 0;
}
01背包问题
特点
n种物品,每种物品只有一件,要么放,要么不放
* dp[i][j]-->前i件物品在背包容量不超过j的前提下产生的最大价值 * 背包容量不够,不放0 * if(j<w[j]) dp[i][j]=dp[i-1][j] * 背包容量够,放不放都行,max(0,1)决策 * else dp[i][j]=max(dp[i-1][j-w[i]]+v[i],dp[i-1][j])
搜索
#include <iostream>
#include <climits>
using namespace std;
const int N=1e2+10;
int m,n,w[N],v[N],mx=INT_MIN;
bool vis[N];
void dfs(int curv,int curw,int depth){
if(curw>m) return;
mx=max(mx,curv);
if(depth==n+1) return;
for(int i=1;i<=n;i++){
if(!vis[i]){
vis[i]=1;
dfs(curv+v[i],curw+w[i],depth+1);
vis[i]=0;
}
}
}
int main(){
cin>>m>>n;
for(int i=1;i<=n;i++){
cin>>w[i]>>v[i];
}
dfs(0,0,1);
cout<<mx<<endl;
return 0;
}
二维01背包DP
#include <iostream>
#include <climits>
using namespace std;
const int N=35,M=2e2+10;
int m,n,w[N],v[N],mx=INT_MIN,dp[N][M];
/*
* dp[i][j]-->前i件物品在背包容量不超过j的前提下产生的最大价值
* 背包容量不够,不放0
* if(j<w[j]) dp[i][j]=dp[i-1][j]
* 背包容量够,放不放都行,max(0,1)决策
* else dp[i][j]=max(dp[i-1][j-w[i]]+v[i],dp[i-1][j])
* */
int main(){
cin>>m>>n;
for(int i=1;i<=n;i++){
cin>>w[i]>>v[i];
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(j<w[i]) dp[i][j]=dp[i-1][j];
else dp[i][j]=max(dp[i-1][j-w[i]]+v[i],dp[i-1][j]);
}
}
cout<<dp[n][m]<<endl;
return 0;
}
01背包的逆推滚动数组优化
dp数组定义的时候省去了i,由于是自己转移给自己,需要逆推转移
#include <iostream>
#include <climits>
using namespace std;
const int N=35,M=2e2+10;
int m,n,w[N],v[N],mx=INT_MIN,dp[M];
/*
* 01 背包的滚动数组优化,自己转移给自己
* 由于是自己转移给自己,如果前面的先被修改了后面的就都错了
* */
int main(){
cin>>m>>n;
for(int i=1;i<=n;i++){
cin>>w[i]>>v[i];
}
for(int i=1;i<=n;i++){
for(int j=m;j>=w[i];j--){
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
}
}
cout<<dp[m]<<endl;
return 0;
}
完全背包
特点
特点:n种物品,每种物品有无限件(但其实是有限 m/w[i])
dp[j]=max{ dp[j-0*w[i]]+0*v[i], dp[j-1*w[i]]+1*v[i], dp[j-2*w[i]]+2*v[i], dp[j-3*w[i]]+3*v[i], ... dp[j-m/w[i]*w[i]]+m/w[i]*v[i] }
逆推朴素版本
#include <iostream>
#include <climits>
using namespace std;
const int N=35,M=2e2+10;
int m,n,w[N],v[N],mx=INT_MIN,dp[M];
//完全背包
//特点:n种物品,每种物品有无限件(但其实是有限 m/w[i])
//状态 dp[j] 前i种物品在背包容量不超过j的情况下的最大价值
//状态转移方程
/*
dp[j]=max{
dp[j-0*w[i]]+0*v[i],
dp[j-1*w[i]]+1*v[i],
dp[j-2*w[i]]+2*v[i],
dp[j-3*w[i]]+3*v[i],
...
dp[j-m/w[i]*w[i]]+m/w[i]*v[i]
}
*/
int main(){
cin>>m>>n;
for(int i=1;i<=n;i++){
cin>>w[i]>>v[i];
}
for(int i=1;i<=n;i++){
for(int j=m;j>=1;j--){
for(int k=1;k<=m/w[i];k++){
//对第i种物品选k件取决策
if(j>=k*w[i])dp[j]=max(dp[j-k*w[i]]+k*v[i],dp[j]);
}
}
}
cout<<dp[m]<<endl;
return 0;
}
正推叠加优化版本
#include <iostream>
#include <climits>
using namespace std;
const int N=40,M=2e2+10;
int m,n,w[N],v[N],dp[M];
//完全背包
//特点:n种物品,每种物品有无限件(但其实是 有限 m/w[i])
//状态 dp[j] 前i种物品在背包容量不超过j的情况下的最大价值
//状态转移方程
/*
dp[j]=max{
dp[j-0*w[i]]+0*v[i],
dp[j-1*w[i]]+1*v[i],
dp[j-2*w[i]]+2*v[i],
dp[j-3*w[i]]+3*v[i],
...
dp[j-m/w[i]*w[i]]+m/w[i]*v[i]
}
*/
int main(){
cin>>m>>n;
for(int i=1;i<=n;i++){
cin>>w[i]>>v[i];
}
for(int i=1;i<=n;i++){
//完全背包z叠加优化
for(int j=w[i];j<=m;j++){
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
}
}
cout<<"max="<<dp[m]<<endl;
return 0;
}
多重背包
特点
n种物品,每种都有固定的个数
朴素版本
#include <iostream>
#include <climits>
using namespace std;
const int N=5e2+10,M=6e3+10;
int m,n,w[N],v[N],mx=INT_MIN,dp[M],s[N];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>w[i]>>v[i]>>s[i];
s[i]=min(s[i],m/w[i]);
}
for(int i=1;i<=n;i++){
for(int j=m;j>=1;j--){
//for(int k=0;k<=1;k++){}0 1 背包
for(int k=1;k<=s[i];k++){
//对第i种物品选k件取决策
if(j>=k*w[i])dp[j]=max(dp[j-k*w[i]]+k*v[i],dp[j]);
}
}
}
cout<<dp[m]<<endl;
return 0;
}
二进制分解物品数优化
#include<iostream>
using namespace std;
//注意,二进制分解后物品种类会增多,所以N的数量级需要开大一些
const int N = 5e3 + 10, M = 6e3 + 10;
int m, n, w[N], v[N], s[N], dp[M];
int ww, vv, ss;
int main() {
cin >> n >> m;
int id = 0;//二进制分解后的物品的种类
//二进制分解,将第i种物品的s件拆分成若干件物品,每件物品只有一件,转换为01背包问题
for (int i = 1; i <= n; i++) {
cin >> ww >> vv >> ss;//输入每种物品的容量、价值、数量
//针对ss进行二进制分解
for (int j = 1; j <= ss;j<<=1) {
w[++id] = j * ww;
v[id] = j * vv;
ss -= j;
}
//如果二进制分解后的ss有剩余,则按剩余ss倍的物品存储
if (ss) {
w[++id] = ss * ww;
v[id] = ss * vv;
ss = 0;
}
}
//注意,二进制分解后的物品种类不再是n,是id
for (int i = 1; i <= id; i++) {
for (int j = m; j >= w[i]; j--) {
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
}
}
cout << dp[m] << endl;
return 0;
}
混合背包
转化为多重背包问题
#include <iostream>
using namespace std;
const int N=40,M=2e2+10;
int w[N],v[N],s[N],m,n,dp[M];
int main(){
cin>>m>>n;
for(int i=1;i<=n;i++){
cin>>w[i]>>v[i]>>s[i];
if(s[i]==0) s[i]=m/w[i];
if(s[i]!=0&&s[i]!=1){
s[i]=min(s[i],m/w[i]);
}
}
for(int i=1;i<=n;i++){
for(int j=m;j>=1;j--){
for(int k=1;k<=s[i];k++){
if(j>=k*w[i]) dp[j]=max(dp[j],k*v[i]+dp[j-k*w[i]]);
}
}
}
cout<<dp[m]<<endl;
return 0;
}
转化为01背包问题
#include<iostream>
using namespace std;
//注意,二进制分解后物品种类会增多,所以N的数量级需要开大一些
const int N = 100, M = 400;
int m, n, w[N], v[N], s[N], dp[M];
int ww, vv, ss;
int main() {
cin >> m >> n;
int id = 0;//二进制分解后的物品的种类
//二进制分解,将第i种物品的s件拆分成若干件物品,每件物品只有一件,转换为01背包问题
for (int i = 1; i <= n; i++) {
cin >> ww >> vv >> ss;//输入每种物品的容量、价值、数量
if(ss==0) ss=m/ww;
//针对ss进行二进制分解
for (int j = 1; j <= ss;j<<=1) {
w[++id] = j * ww;
v[id] = j * vv;
ss -= j;
}
//如果二进制分解后的ss有剩余,则按剩余ss倍的物品存储
if (ss) {
w[++id] = ss * ww;
v[id] = ss * vv;
ss = 0;
}
}
//注意,二进制分解后的物品种类不再是n,是id
for (int i = 1; i <= id; i++) {
for (int j = m; j >= w[i]; j--) {
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
}
}
cout << dp[m] << endl;
return 0;
}
二维费用背包
#include<iostream>
#include<cstring>
using namespace std;
/*
二维费用背包(基础就是01背包)
状态dp[j][k] 前i种物品在背包1容量不超过j的情况且背包2容量不超过k的情况下构成的最小价值
状态转移方程 dp[j][k]=min(dp[j][k],dp[j-w1[i]][k-w2[i]]+v[i]);
*/
const int N = 1e3 + 10, M = 1e2 + 10;
int n, m1, m2;
int w1[N], w2[N], v[N], dp[M][M];
int main() {
cin >> m1 >> m2 >> n;
for (int i = 1; i <= n; i++)
cin >> w1[i] >> w2[i] >> v[i];
//注意,本题求最小价值,dp一定初始化为最大
memset(dp, 0x3f, sizeof dp);
dp[0][0] = 0;
for (int i = 1; i <= n; i++)
for (int j = m1; j >= 0; j--)
for (int k = m2; k >= 0; k--)
dp[j][k] = min(dp[j][k], dp[max(0,j - w1[i])][max(0,k - w2[i])] + v[i]);
cout << dp[m1][m2] << endl;
return 0;
}
分组背包
#include<iostream>
#include<vector>
using namespace std;
/*
二维费用背包(基础就是01背包)
状态dp[j][k] 前i种物品在背包1容量不超过j的情况且背包2容量不超过k的情况下构成的最小价值
状态转移方程 dp[j][k]=min(dp[j][k],dp[j-w1[i]][k-w2[i]]+v[i]);
*/
const int N = 40, M = 210;
int n, m,p,t;
int w[N], v[N], dp[M];
int main() {
cin>>m>>n>>t;
vector<int> group[N];
for (int i = 1; i <= n; i++){
cin >> w[i] >> v[i] >>p;
group[p].push_back(i);
}
for (int i = 1; i <= t; i++)
for (int j = m; j >= 1; j--){
for(int k=0;k<group[i].size();k++){
int id=group[i][k];
if(j>=w[id]) dp[j]=max(dp[j],v[id]+dp[j-w[id]]);
}
}
cout << dp[m] << endl;
return 0;
}
有依赖背包
特点:
有主件有附件,每种物品只有一件
要么不选主件
要么选主件不选附件
要么选主键和第一件附件
要么选主键和第二件附件
要么选主键、第一件附件和第二件附件
5中情况之中区max
[P1064 NOIP2006 提高组] 金明的预算方案 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
#include <iostream>
using namespace std;
const int N=100,M=1e5+10;
int m,n,main_w[N],main_v[N],sec_w[N][3],sec_v[N][3],dp[M],cnt[N];
int main(){
cin>>m>>n;
for(int i=1;i<=n;i++){
int ww,vv,q;
cin>>ww>>vv>>q;
if(q==0){//主件
main_w[i]=ww;
main_v[i]=ww*vv;
}
else{//附件,那么q附件对应就是主要的编号
cnt[q]++;//第q个主件的第cnt[q]个附件
sec_w[q][cnt[q]]=ww;
sec_v[q][cnt[q]]=ww*vv;
}
}
for(int i=1;i<=n;i++){
for(int j=m;j>=1;j--){
//选主件,不选附件
if(j>=main_w[i]) dp[j]=max(dp[j],main_v[i]+dp[j-main_w[i]]);
//选主键和第一个附件
if(j>=main_w[i]+sec_w[i][1]) dp[j]=max(dp[j],main_v[i]+sec_v[i][1]+dp[j-(main_w[i]+sec_w[i][1])]);
//选主键和第二个附件
if(j>=main_w[i]+sec_w[i][2]) dp[j]=max(dp[j],main_v[i]+sec_v[i][2]+dp[j-(main_w[i]+sec_w[i][2])]);
//选主键和第一个附件和第二个附件
if(j>=main_w[i]+sec_w[i][1]+sec_w[i][2]) dp[j]=max(dp[j],main_v[i]+sec_v[i][1]+sec_v[i][2]+dp[j-(main_w[i]+sec_w[i][1]+sec_w[i][2])]);
}
}
cout<<dp[m]<<endl;
return 0;
}
背包求方案数
背包
#include <iostream>
using namespace std;
const int N=1e5+10;
int m,n,w[N],dp[N];
//01背包求方案数
//dp[j]-->前i个数组组合出数字j的方案数
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>w[i];
}
dp[0]=1;
for(int i=1;i<=n;i++){
for(int j=m;j>=w[i];j--){
dp[j]=d[j]+dp[j-w[i]];
// 不选第i个+x
}
}
cout<<dp[m]<<endl;
return 0;
}
dfs
#include <iostream>
using namespace std;
const int N=25;
int a[N],ans=0,n,vis[N],cnt[N],sum;
void dfs(int depth,int cursum,int start){
if(cursum>sum||depth>n) return;
if(cursum==sum){
ans++;
return;
}
for(int i=start;i<=n;i++){
if(!vis[i]){
vis[i]=1;
cnt[depth]=a[i];
dfs(depth+1,cursum+a[i],i);
vis[i]=0;
}
}
}
int main(){
cin>>n>>sum;
for(int i=1;i<=n;i++){
cin>>a[i];
}
dfs(1,0,1);
if(sum==0) cout<<1<<endl;
else cout<<ans<<endl;
return 0;
}
背包求具体方案
#include<iostream>
using namespace std;
const int N = 40, M = 2e2 + 10;
int m, n, w[N], v[N], dp[N][M];//注意求具体方案时,需要二维dp
//背包求具体方案
int main() {
cin >> m >> n;
for (int i = 1; i <= n; i++) cin >> w[i] >> v[i];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (j < w[i]) dp[i][j] = dp[i - 1][j];//放不下,不放
else dp[i][j] = max(dp[i - 1][j], dp[i-1][j - w[i]] + v[i]);
}
}
//cout << dp[n][m] << endl;
//打印具体方案
int j = m;
for (int i = n; i >= 1; i--) {
if (dp[i][j] == dp[i - 1][j - w[i]] + v[i]) {
cout << i << " ";
j -= w[i];
}
}
return 0;
}