背包九讲(动态规划)

闫氏DP分析法

在这里插入图片描述

DP问题主要是靠经验的,所以要多加练习总结

1.01背包问题

题目链接

在这里插入图片描述

f[i][j]表示只看前i个物品,总体积是j的情况下,总价值最大是多少

result=max(f[n][0v])

f[i][j];

1.不选第i个物品,f[i][j]=f[i-1][j];

2.选第i个物品,f[i][j]=f[i-1][j-v[i]]+w[i];

朴素版代码

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1010;
int n,m;
int f[N][N],v[N],w[N];
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		cin>>v[i]>>w[i];
	for(int i=1;i<=n;i++)//默认f[0][0]为0
	for(int j=0;j<=m;j++)
	{
		f[i][j]=f[i-1][j];
		if(j>=v[i])//有空间才行
			f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);//有i无i作比较
			
	 } 
	int res=0;
	for(int i=0;i<=m;i++)
		res=max(res,f[n][i]);
	cout<<res<<endl;//直接输出f[n[[m]也行;
	return 0;
 } 

优化版

很容易想到吧if判定条件放在循环的时候,可以把数组f变为一维的,因为i=i-1,可以知道,都是从上一个状态转移过来的但是j 不是上一个转移的(本来i=i-1,即j对于的是上一个i,现在把i去掉),j 可以从大到小,这样就上从上一个转移过来的

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1010;
int n,m;
int f[N],v[N],w[N];
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		cin>>v[i]>>w[i];
	for(int i=1;i<=n;i++)
		for(int j=m;j>=v[i];j--)//从大到小
			f[j]=max(f[j],f[j-v[i]]+w[i]);
	cout<<f[m]<<endl;
	return 0;
 } 

一个循环

其实就是简单变形

#include<iostream>
using namespace std;
int f[10000];
int main()
{
	int N,V;
	cin>>N>>V;
	for(int i=1;i<=N;i++)
	{
		int v,w;
		cin>>v>>w;
		for(int j=V;j>=v;j--)
			f[j]=max(f[j],f[j-v]+w);
		
	}
	cout<<f[V]<<"\n";
	return 0;
}

2.完全背包

题目链接
与01背包的差别就是这个可以无限选
在这里插入图片描述
前i-1项已经定型,第i项选k个i ,选第i个的时候空间还有v-k*i;
可以得出f[i][j]=f[i-1][j],f[i][j]=f[i-1][j-v]+w
推导出
f[i][j] = max(f[i-1][j],f[i-1][j-v]+w,f[i-1][j-2v]+2w,…)
f[i][j-v] = max(f[i-1][j-v],f[i-1][j-2v]+w,f[i-1][j-3v]+2w…)

第一个式子的第2项和第二个式子的第一项作比较,发现只相差一个w
推导为f[i,j] = max(f[i-1][j],f[i][j-v]+w);

朴素代码

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1010;
int n,m;
int f[N][N],v[N],w[N];
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		cin>>v[i]>>w[i];
	for(int i=1;i<=n;i++)
	for(int j=0;j<=m;j++)
	{
		f[i][j]=f[i-1][j];
		if(j>=v[i])
			f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]);//
			
	 } 
	cout<<f[n][m]<<endl;
	return 0;
 } 

优化代码

优化思路:先把if判断消去,而且i和j是等价的,所以数组f可以直接变为一维

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1010;
int n,m;
int f[N],v[N],w[N];
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		cin>>v[i]>>w[i];
	for(int i=1;i<=n;i++)
		for(int j=v[i];j<=m;j++)
			f[j]=max(f[j],f[j-v[i]]+w[i]);
	cout<<f[m]<<endl;
	return 0;
 } 

一个循环

#include<iostream>
using namespace std;
int f[10000];
int main()
{
	int N,V;
	cin>>N>>V;
	for(int i=1;i<=N;i++)
	{
		int v,w;
		cin>>v>>w;
		for(int j=v;j<=V;j++)
			f[j]=max(f[j],f[j-v]+w);
		
	}
	cout<<f[V]<<"\n";
	return 0;
}

3.多重背包 1

题目链接
在这里插入图片描述
每个物品随便选,可以想到第i件物品选s[i]个依次比较
可以得到公式**f[i][j]=max(f[i][j],f[i-1][j-v[i]k]+w[i]k)

朴素代码

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1010;
int n,m;
int f[N][N],v[N],w[N],s[N];
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		cin>>v[i]>>w[i]>>s[i];
	for(int i=1;i<=n;i++)
		for(int j=0;j<=m;j++)
			for(int k=0;k<=s[i]&&k*v[i]<=j;k++)
				f[i][j]=max(f[i][j],f[i-1][j-v[i]*k]+w[i]*k);
	cout<<f[n][m]<<endl;
	return 0;
 } 

4.多重背包2

题目链接
其实就是数据变大的多重背包1

优化代码(背包1的优化)

倍增法

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=25000;
int n,m;int v[N],w[N]; int f[N];
int main()
{
	 cin>>n>>m;
	 int cnt=0;
	 for(int i=1;i<=n;i++)
	 {
	 	int a,b,s;
	 	cin>>a>>b>>s;
	 	int k=1;
	 	while(k<=s)
	 	{
	 		cnt++;
	 		v[cnt]=a*k;
	 		w[cnt]=b*k;
	 		s-=k;
	 		k*=2;
		}
		if(s>0)
		{
			cnt++;
			v[cnt]=a*s;
			w[cnt]=b*s;
		 } 
	 }
	 for(int i=1;i<=cnt;i++)
	 	for(int j=m;j>=v[i];j--)
	 	    f[j]=max(f[j],f[j-v[i]]+w[i]);
	cout<<f[m]<<endl;
	return 0;
}

优化代码(背包1的优化)

单调队列,思路和上面代码一样,用容器和结构体,但是比上面的慢

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 2022;
int n, m, f[N];
struct Good {
    int v, w;
};
int main() 
{
    vector<Good> goods;
    cin >> n >> m;
    for (int i = 0; i < n; i++) 
    {
        int v, w, s;
        cin >> v >> w >> s;
        for (int j = 1; j <= s; j++) 
        {
            s -= j;
            goods.push_back({v * j, w * j});
        }
        if (s > 0) goods.push_back({v * s, w * s});
    }
    for (auto good : goods) 
        for (int j = m; j >= good.v; j--) 
            f[j] = max(f[j], f[j - good.v] + good.w);
    cout << f[m] << endl;
    return 0;
}

5.多重背包3

题目链接

朴素代码

其实就是多重背包的终极优化

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=20020;
int n,m,f[N],g[N],q[N];
int main()
{
	cin>>n>>m;
	for(int i=0;i<n;i++)
	{
		int v,w,s;
		cin>>v>>w>>s;
		memcpy(g,f,sizeof(f));
		for(int j=0;j<v;j++)
		{
			int hh=0,tt=-1;
			for(int k=j;k<=m;k+=v)	
			{
				f[k]=g[k];
				if(hh<=tt&&k-s*v>q[hh]) hh++;
				if(hh<=tt) f[k]=max(f[k],g[q[hh]]+(k-q[hh])/v*w);
				while(hh<=tt&&g[q[tt]]-(q[tt]-j)/v*w<=g[k]-(k-j)/v*w) tt--;
				q[++tt]=k;
			}
		}		
	}
	cout<<f[m]<<'\n';
	return 0;
}
 

6.混合背包

题目链接
混合背包就是把01背包,完全背包,多重背包组合起来,判断一下不同情况就行

朴素代码

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 1111;
int n, m, f[N];
struct Thing 
{
    int kind;
    int v, w;
};
vector<Thing> things;
int main() 
{
    cin >> n >> m;
    for (int i = 0; i < n; i++) 
    {
        int v, w, s;
        cin >> v >> w >> s;
        if (s < 0)
         {
            things.push_back({-1, v, w});
        } 
        else if (s == 0) 
        {
            things.push_back({0, v, w});//判断不同情况入栈
        } 
        else //多重背包,拆分为多个01背包
        {
            for (int k = 1; k <= s; k *= 2) 
            {
                s -= k;
                things.push_back({-1, v * k, w * k});
            }
            if(s>0)things.push_back({-1, v * s, w * s});//最后剩余的
        }
    }
    for (const auto& thing : things) //赋值
    {
        if (thing.kind < 0) 
        {
            for (int j = m; j >= thing.v; j--) 
            {
                f[j] = max(f[j], f[j - thing.v] + thing.w);//01背包求最大值
            }
        } 
        else 
        {
            for (int j = thing.v; j <= m; j++) 
            {
                f[j] = max(f[j], f[j - thing.v] + thing.w);//多重背包求最大值
            }
        }
    }
    cout << f[m] << '\n';
    return 0;
}

7.二维费用的背包问题

题目链接
其实和01背包一样,就加了一个限制条件(重量)
就这个核心公式: f[i][j1][j2] = max (f[i][j1][j2],f[i - 1][j1 - w1][j2 - w2] + v)

朴素代码

#include <iostream>
using namespace std;
const int N = 110;
int n,m1,m2;
int f[1010][N][N];
int main () {
    cin >> n >> m1 >> m2;
    for (int i = 1;i <= n;i++) {
        int w1,w2,v;
        cin >> w1 >> w2 >> v;
        for (int j1 = m1;j1 >= 0;j1--) {
            for (int j2 = m2;j2 >= 0;j2--) {
                f[i][j1][j2] = f[i - 1][j1][j2];
                if (j1 >= w1 && j2 >= w2) 
                	f[i][j1][j2] = max (f[i][j1][j2],f[i - 1][j1 - w1][j2 - w2] + v);
            }
        }
    }
    cout << f[n][m1][m2] << endl;
    return 0;
}

优化代码

减少不必要循环

#include<iostream>
#include<algorithm>
using namespace std;
const int N=111;
int n,v,m,f[N][N];
int main()
{
	cin>>n>>v>>m;
	for(int i=0;i<n;i++)
	{
		int a,b,c;
		cin>>a>>b>>c;
		for(int j=v;j>=a;j--)
			for(int k=m;k>=b;k--)
			f[j][k]=max(f[j][k],f[j-a][k-b]+c);
	}
	cout<<f[v][m]<<'\n';
	return 0;
}

8.分组背包问题

题目链接
其实就是多个01背包,每一组可以选也可以不选,就是第i组选第k个
在这里插入图片描述
式子1:f[i-1][j]
式子2:f[i-1][j-v[i][k]]+w[i][k]
f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);

朴素代码

#include<iostream>
#include<algorithm>
using namespace std;
const int N=191;
int n,m,v[N][N],w[N][N],s[N],f[N];
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>s[i];
		for(int j=0;j<s[i];j++)
			cin>>v[i][j]>>w[i][j];
	}
	for(int i=1;i<=n;i++)
		for(int j=m;j>=0;j--)
			for(int k=0;k<s[i];k++)
			    if(v[i][k]<=j)
			    	f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);
	cout<<f[m]<<endl;
	return 0;
}

朴素代码

其实和上面前一样

#include<iostream>
#include<algorithm>
using namespace std;
const int N=190;
int n,m,v[N],w[N],s,f[N];
int main()
{
	cin>>n>>m;
	for(int i=0;i<n;i++)
	{
		cin>>s;
		for(int j=0;j<s;j++)
			cin>>v[j]>>w[j];
		for(int j=m;j>=0;j--)
			for(int k=0;k<s;k++)
			if(v[k]<=j)
				f[j]=max(f[j],f[j-v[k]]+w[k]);
	}
	cout<<f[m]<<endl;
	return 0;
}

9.有依赖的背包问题

题目链接
树形DP和背包问题的结合

朴素代码

#include <iostream>
#include <vector>

using namespace std;

int f[110][110]; // f[x][v] 表示选择以 x 为子树的物品,在容量不超过 v 时所获得的最大价值
vector<int> g[110];//vector<int> g[110] 用于构建树的邻接表,存储每个节点的子节点。
int v[110], w[110];//v[110] 数组存储每个物品(节点)的体积,w[110] 数组存储每个物品的价值。
int n, m, root;

int dfs(int x, int m) 
{
    for (int i = v[x]; i <= m; i++) f[x][i] = w[x]; // 点 x 必须选,所以初始化 f[x][v[x] ~ m] = w[x]
    for (int i = 0; i < g[x].size(); i++) 
    {
        int y = g[x][i];
        dfs(y, m - v[x]);//,对于每个子节点 y ,递归调用 dfs(y, m - v[x]) ,即计算子节点在剩余容量下的最大价值。
       
       //两层循环更新当前节点 x 在不同容量 j的最大价值。内层循环枚举分配给子节点 y 的容量 k ,通过比较更新 f[x][j] 的值。
        for (int j = m; j >= v[x]; j--) 
        { // j 的范围为 v[x] ~ m, 小于 v[x] 无法选择以 x 为子树的物品
            for (int k = 0; k <= j - v[x]; k++) 
            { // 分给子树 y 的空间不能大于 j - v[x], 不然都无法选根物品 x
                if (f[x][j - k] + f[y][k] > f[x][j]) 
                { 
                    f[x][j] = f[x][j - k] + f[y][k];
                }
            }
        }
    }
    return f[x][m];
}

int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) 
    {
        int ff;
        cin >> v[i] >> w[i] >> ff;
        if (ff == -1)
            root = i;
        else
            g[ff].push_back(i);
    }
    cout << dfs(root, m);
    return 0;
}

10.背包问题求方案数

题目链接
01背包换了一种问法,就是找到最大值之后确定有几个最大值

朴素代码

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1111,mod=1000000007,INF=1000000;
int n,m;
int f[N],g[N];
int main()
{
	cin>>n>>m;
	g[0]=1;
	for(int i=1;i<=m;i++)
		f[i]= -INF;
	for(int i=0;i<n;i++)
	{
		int v,w;
		cin>>v>>w;
		for(int j=m;j>=v;j--)
		{
			int t=max(f[j],f[j-v]+w);//选和不选的最大值
			int s=0;
			if(t==f[j]) s+=g[j];//第一种决策
			if(t==f[j-v]+w) s+=g[j-v];//第二种决策
			if(s > mod) s -= mod;//取模
			f[j]=t;//最优解
			g[j]=s;//方案数
		}
	}
	int maxw = 0;
	for(int i = 0;i <= m;i ++)
	maxw=max(maxw,f[i]);//求最优解,体积不一定用完
	int res=0;
	for(int i = 0;i <= m;i ++)
	if(maxw==f[i])//所有最优解方案
	{
		res+=g[i];
		if(res>=mod) res -= mod;//取模
	}
	cout<<res<<'\n';
	return 0;
} 

11.背包问题求具体方案

题目链接
在这里插入图片描述
和01完全一样,就是找一下具体是选哪一个,不过要注意一下字典序最小,正常求法是最大,所以逆过来

朴素代码

#include<iostream>
#include<algorithm>

using namespace std;
const int N=1111;
int n ,m,f[N][N],v[N],w[N];
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		cin>>v[i]>>w[i];
	for(int i=n;i>=0;i--) //因为要求字典序最小,所以这里逆序
		for(int j=0;j<=m;j++)
		{
			f[i][j]=f[i+1][j];
			if(j>=v[i])
				f[i][j]=max(f[i][j],f[i+1][j-v[i]]+w[i]);
		}
	int i=1,j=m;
	while(i<=n)
	{
		if(j>=v[i]&&f[i+1][j-v[i]]+w[i]>=f[i+1][j])//右边可以取到最大值
		{
			cout<<i<<' ';
			j-=v[i];
			i++;
		}
		else i++;//不能选择第i个物品(左边集合)
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值