首页 > 其他分享 >DP总结

DP总结

时间:2024-02-17 20:15:22浏览次数:38  
标签:总结 背包 int max 件物品 maxn 物品 DP

DP总结

DP(动态规划)简介

动态规划是一种通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。
由于动态规划并**不是某种具体的算法**,而是一种解决特定问题的方法,因此它会出现在各式各样的数据结构中,与之相关的题目种类也更为繁杂。

DP基础

1.必要前提
	需要满足三个条件:最优子结构,无后效性和子问题重叠。 
2.基本思路
	1.将原问题划分为若干 **阶段**,每个阶段对应若干个子问题,提取这些子问题的特征(称之为 状态);
	2.寻找每一个状态的可能 **决策**,或者说是各状态间的相互转移方式(用数学的语言描述就是 **状态转移方程**)。
	3.按顺序求解每一个阶段的问题。

各种DP

背包DP

01背包

image

朴素版
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1010;
//f[i][j]表示前i个物品,体积不超过j时的最大价值
//不选第i个物品时,f[i][j] = f[i-1][j]
//选第i个物品时,f[i][j] = f[i-1][j-v[i]]+w[i],保证j>=v[i] 
int f[maxn][maxn] = {};	//默认全为0,这样后面就不需要再初始化
int n = 0, m = 0;	//n件物品,m为背包总容量
int v[maxn] = {}, w[maxn] = {};	//v表示第i件物品体积,w为第i件物品价值
int main()
{	
	scanf("%d%d", &n, &m);
	for(int i=1; i<=n; i++) scanf("%d%d", &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-1][j-v[i]] + w[i]);
		}
	}
	printf("%d", f[n][m]);

	return 0;
}
滚动数组优化版
因为每次的动态转移只与i-1层(前i-1个物品)的dp值相关 所以可用二维数组模拟滚动数组以减少内存
终极版

我们发现物品枚举顺序跟结果无关,枚举体积时先枚举体积大的还是小的也不影响最后结果,如果我们枚举体积的时倒序枚举,那在第二层循环中f[] 之前的位置(f[1]~f[j-1])都是在选前i-1件物品是的无后效性的dp值,这样我们就可以省去一维数组,我们用f[j]表示处理当前第i件物品时体积为j的最大价值,递推公式:f[j]=max(f[j],f[j-v[i]]+w[i])。表达式右边的f[j],f[j-v[i]]表示处理完上个物品之后的结果,由于倒序处理处理体积j的时候f[j], f[j-v[i]]还保留着上一行的状态

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1010;
//f[i][j]表示前i个物品,体积不超过j时的最大价值
//不选第i个物品时,f[i][j] = f[i-1][j]
//选第i个物品时,f[i][j] = f[i-1][j-v[i]]+w[i],保证j>=v[i] 
int f[maxn][maxn] = {};	//默认全为0,这样后面就不需要再初始化
int n = 0, m = 0;	//n件物品,m为背包总容量
int v[maxn] = {}, w[maxn] = {};	//v表示第i件物品体积,w为第i件物品价值
int main()
{	
	scanf("%d%d", &n, &m);
	for(int i=1; i<=n; i++) scanf("%d%d", &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-1][j-v[i]] + w[i]);
		}
	}
	printf("%d", f[n][m]);
	return 0;
}

完全背包

	完全背包模型与 0-1 背包类似,与 0-1 背包的区别仅在于一个物品可以选取无限次,而非仅能选取一次。
	我们可以借鉴 0-1 背包的思路,进行状态定义:设 f_{i,j} 为只能选前 i 个物品时,容量为 j 的背包可以达到的最大价值。
朴素版 与01背包类似 不过需要k来表示选出的多种物品

状态转移方程 f[i][j] = max(f[i][j], f[i-1][j-kv[i]] + kw[i]);

终极版
考虑做一个简单的优化。可以发现,对于 f(i,j),只要通过 f(i,j-w_i) 转移就可以了

image

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1010;
int f[maxn] = {};	//默认全为0,这样后面就不需要再初始化
int n = 0, m = 0;	//n件物品,m为背包总容量
int v[maxn] = {}, w[maxn] = {};	//v表示第i件物品体积,w为第i件物品价值
int main()
{	
	scanf("%d%d", &n, &m);
	for(int i=1; i<=n; i++) scanf("%d%d", &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]);
		}
	}
	printf("%d", f[m]);
	return 0;

!!!第二维倒序是0/1背包,正序是完全背包!

多重背包

01背包类似 不过要多放相应的物品数量

想到二进制可以表示所有数 所以用二进制对物品数量进行拆分
此处二进制主要解决超时的问题
但要注意 每次分的一种二进制拆分的个数只能有一个,并且是连续(除了无法整分的所剩的部分);
比如说
虽然 8可以用二进制表示为1000;
但不能只用一个8倍的该物品解决
应该改为用1倍 2倍 4倍 1倍解决 以完整表示1~8所有数

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 15000;
const int maxm = 2010;
int n = 0, m = 0;	 
int f[maxm] = {};	 
int v[maxn] = {}, w[maxn] = {}, s[maxn] = {}, cnt = 0;	 
int main()
{	
	int vi = 0, wi = 0, si = 0;
	scanf("%d%d", &n, &m);
	//二进制拆分
	for(int i=1; i<=n; i++)
	{
		scanf("%d%d%d", &vi, &wi, &si);
		if(si > m / vi) si = m / vi;
		for(int j=1; j<=si; j<<=1)
		{
			v[++cnt] = j * vi;
			w[cnt] = j * wi;
			si -= j;
		}
		if(si > 0)
		{
			v[++cnt] = si * vi;
			w[cnt] = si * wi;
		}
	}
	//0/1背包
	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]);
		}	
	}
	printf("%d", f[m]);
	return 0;
}

混合背包

之前三种背包的混合 统一转为多重背包
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 15000;
const int maxm = 2010;
int n = 0, m = 0;	 
int f[maxm] = {};	 
int v[maxn] = {}, w[maxn] = {}, s[maxn] = {}, cnt = 0;	 
int main()
{	
	int vi = 0, wi = 0, si = 0;
	scanf("%d%d", &n, &m);
	//二进制拆分
	for(int i=1; i<=n; i++)
	{
		scanf("%d%d%d", &vi, &wi, &si);
		if(si > m / vi) si = m / vi;
		for(int j=1; j<=si; j<<=1)
		{
			v[++cnt] = j * vi;
			w[cnt] = j * wi;
			si -= j;
		}
		if(si > 0)
		{
			v[++cnt] = si * vi;
			w[cnt] = si * wi;
		}
	}
	//0/1背包
	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]);
		}	
	}
	printf("%d", f[m]);
	return 0;
}

分组背包(最多选一件)

Ø 每组物品要么一件不取,要么只取其中的一件,跟0/1背包很类似,0/1背包是对单个物品而言,而分组背包是对一组而言
Ø 定义f[i][j]表示第i组物品在背包容量为j对第i组的第k个物品进行决策的最大价值
Ø 动态转移方程为:f[i][j]=max(f[i-1][j],f[i-1][j-w[g[i][k]]]+c[g[i][k]])

朴素版直接跳过
优化版本1
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 40;
const int maxm = 210;
//分组背包
int n = 0, m = 0, t = 0;	 
int v[maxn] = {}, c[maxn] = {}, g[15][maxn] = {};
int f[maxm] = {};
int main()
{	 
	int x = 0;
	scanf("%d%d%d", &m, &n, &t); 
	for(int i=1; i<=n; i++)
	{
		scanf("%d%d%d", &v[i], &c[i], &x);
		g[x][++g[x][0]] = i;
	}
	for(int i=1; i<=t; i++)
	{
		for(int j=m; j>=0; j--)
		{
			for(int k=1; k<=g[i][0]; k++)
			{
				if(j >= v[g[i][k]]) 
				{
					x = g[i][k];
					f[j] = max(f[j], f[j-v[x]] + c[x]);	
				}
			}
		}
	}
	printf("%d", f[m]);
	return 0;
}
优化版本2
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 110;
const int maxm = 110;
int n = 0, m = 0;	 
int f[maxm] = {};	 
int v[maxn][maxn] = {}, w[maxn][maxn] = {}, s[maxn] = {};	 
int main()
{	 
	scanf("%d%d", &n, &m);
	for(int i=1; i<=n; i++)
	{
		scanf("%d", &s[i]);
		for(int j=1; j<=s[i]; j++)
		{
			scanf("%d%d", &v[i][j], &w[i][j]);
		}
	}
	for(int i=1; i<=n; i++)	//阶段
	{
		//i和j共同构成状态
		for(int j=m; j>=0; j--)
		{
			for(int k=1; k<=s[i]; k++)	//k是决策
			{
				if(j >= v[i][k])
				{
					f[j] = max(f[j], f[j-v[i][k]] + w[i][k]);
				}
			}
		}
	}
	printf("%d", f[m]);
	return 0;
}

分组背包(至少选一件)

Ø 基础的分组背包是每组至多选一件的最优,而此题要求是每组至少选1件Ø 我们把分组背包的第二和第三层循环换一下位置,意义就变成了每组物品可以多选,但每一件至多选一次
Ø 那如何解决每组里至少选一件呢??
Ø 定义f[i][j]表示前i种品牌,每种至少选了一件有j的钱买鞋的价值最大。Ø 动态转移方程:如果当前第i组里已经选了至少一件物品,那么接下来就是追求价值最大化,可以0/1背包一样同一组里可以追加能买的最大价值,如果当前组一件也没有买就需要买当前物品并从上一组转移过来
Ø 那我们如何判断当前组至少买了一件和上一组的至少买了一件呢?Ø 我们初始化f数组为-1,f[0][0...V]=0,动态转移方程可以写为• f[i][j]=max(f[i][j],f[i][j-v[g[i][k]]+c[g[i][k]] (f[i][j-v[g[i][k]]!=-1)
• f[i][j]=max(f[i][j],f[i-1][j-v[g[i][k]]+c[g[i][k]] (f[i-1][j-v[g[i][k]]!=-1)
Ø 坑点:在一些题中卖价可以为0,这样两个转移方程的顺序就不能变,因为如果先转移下面的状态可能第i组的第k件物品被选两次

例题

I love sneakers!

二维费用背包(有两种限制条件的背包)

与01背包类似 但因其需要考虑到两种限制条件 所以需要多加入一层循环 以配合其dp的转移

eg.NASA的食物计划
点击查看代码
//二维费用背包
int n = 0, v = 0, m = 0;	 
int a[maxn] = {}, b[maxn] = {}, c[maxn] = {};
int f[maxm][maxm] = {};
int main()
{	 
	scanf("%d%d%d", &v, &m, &n);
	for(int i=1; i<=n; i++)
	{
		scanf("%d%d%d", &a[i], &b[i], &c[i]);
	}
	for(int i=1; i<=n; i++)
	{
		for(int j=v; j>=a[i]; j--)
		{
			for(int k=m; k>=b[i]; k--)
			{
				f[j][k] = max(f[j][k], f[j-a[i]][k-b[i]] + c[i]);
			}
		}
	}
	printf("%d", f[v][m]);
	return 0;
}

背包内物品的组合方案数

砝码称重
coins

在coin中要注意其剪枝 运用一个used数组 去省略第三层循环对每种硬币的数量的判断

在该类问题中 可用一个vis数组表示其中可以到达的方案 最好统计总数

背包方案的输出

CD
Buy the souvenirs
方法一

建立bool二维pre数组 pre[i][j]表示价值为j的最优方案中是否选到i

点击查看代码
if(f[j]<f[j-v[i]]+v[i])
{
	f[j]=f[j-v[i]]+v[i];
	pre[i][j]=1; 
}
.
.
.
for(int i=m,j=n;i>=1;i--)
{
	if(pre[i][j]) 
	{
		cout<<v[i]<<' ';
		j-=v[i];
		}
}
方法二
点击查看代码
if(状态转移成功) pre[j]=i;
...
while(最后的点t)
{
	...
	t=pre[t];
}

背包问题第k优解

Bone Collector II

参考之前分治算法中的归并算法
求第k大时 不能只考虑将dp[i][j]设为第k优的值
因为最终答案里的第k大的值不一定由每次的k值累加而来
而是考虑将前k优的值都存储起来最终进行选择

点击查看代码
cin>>n>>m>>k;
for(int i=0;i<n;i++)
{
	cin>>w[i];
}
for(int i=0;i<n;i++)
{
	cin>>v[i];
}
for(int i=0;i<n;i++)
{
  	for(int j=m;j>=v[i];j--)
	  {
  		int p,x,y,z;
  		for(p=1;p<=k;p++)
		{   //对k个数进行状态转移 
	      		a[p]=f[j-v[i]][p]+w[i];
	      		b[p]=f[j][p];
	   	}
	    a[p]=b[p]=-1;  //二分合并 
	   	x=y=z=1;
	    while(z<=k&&(a[x]!=-1||b[y]!=-1))
		{
	      	if(a[x]>b[y])
			{
	        	f[j][z]=a[x++];
	       	}
	      	else
			{
	        	f[j][z]=b[y++];
	        }
	      	if(f[j][z]!=f[j][z-1]){
			z++;
			}
		}
	}
}
cout<<f[m][k];

标签:总结,背包,int,max,件物品,maxn,物品,DP
From: https://www.cnblogs.com/CTHoi/p/18018282

相关文章

  • 【测试运维】性能测试经验文档总结第3篇:VuGen详解(已分享,附代码)
    本系列文章md笔记(已分享)主要讨论性能测试相关知识。入门阶段:认识性能测试分类-(负载测试、压力测试、并发测试、稳定性测试),常用性能测试指标-(吞吐量、并发数、响应时间、点击数...),性能测试工具选择。性能脚本:1.LoadRunner介绍,2.脚本录制、运行、参数化,3.关联、检查点、事务......
  • 【测试运维】性能测试经验文档总结第3篇:VuGen详解(已分享,附代码)
    本系列文章md笔记(已分享)主要讨论性能测试相关知识。入门阶段:认识性能测试分类-(负载测试、压力测试、并发测试、稳定性测试),常用性能测试指标-(吞吐量、并发数、响应时间、点击数...),性能测试工具选择。性能脚本:1.LoadRunner介绍,2.脚本录制、运行、参数化,3.关联、检查点、事务......
  • 第三四周训练总结
    第三周是过年之前的最后一周,所以我也是憋足了劲写题。而第一次牛客组队合作写题也是令我印象深刻。虽然有点坐牢但合作的感觉还是不错的。题目也是难易分明,能在难题上看出自己的不足。而年前最后一次比赛似乎是想让我们过个好年,题目也变得简单了许多,但是有些简单的题我没把握好,确......
  • 区间dp
    Ø合并:意思就是将两个或多个部分进行整合,当然也可以反过来,也就是是将一个问题进行分解成两个或多个部分。Ø特征:能将问题分解成为两两合并的形式Ø求解:对整个问题设最优值,枚举合并点,将问题分解成为左右两个部分,最后将左右两个部分的最优值进行合并得到原问题的最优值。有点类......
  • 区间dp
    合并类动态规划合并:意思就是将两个或多个部分进行整合,当然也可以反过来,也就是是将一个问题进行分解成两个或多个部分。特征:能将问题分解成为两两合并的形式求解:对整个问题设最优值,枚举合并点,将问题分解成为左右两个部分,最后将左右两个部分的最优值进行合并得到原问题的最优......
  • 区间dp
    1.合并石子(1)排成一列的石子这个与合并果子唯一的不同就是只能合并相邻的石子,所以贪不得(怎么所有类型的动规要先上来搞贪心,有点diss贪心的感觉)f[i][j]表示i到j间合并的最大/最小得分;核心for(intlen=2;len<=n;len++){//表示长度2到len时的最大 for(inti=1;i+len-1<=n;i++)......
  • 背包dp
    01背包描述:有n个物品,每个物品只有一件,第i个物品体积为vi,价格为ci。现在有一个体积为V的背包,请你从n件物品里选出若干件放进背包里,使得背包里的物品价值最大。思路:01背包的特点是:每种物品只有一件,可以选择放或不放。我们可以根据此特点进行动态规划(DP),设f[i][j]表示前i件物品放......
  • 动态规划-DP 完整版
    动态规划学完了五大基础dp做个简单总结dp特征动态规划问题首要是找到做题的目的是求最大/小值还是其他;其二要确定问题的状态转移方程这是关键;第三为dp数组找到边界、最后检查是否需要结合其他相关知识如树dfs等;别忘了检查多测输入数组变量置零等易错点。......
  • 线性dp
    线性动态规划:不用多说,主要应用于求上升子序列,下降子序列等直接看例题:样例输入:13791638243718441921226315样例输出:max=879161819212263解:#include<bits/stdc++.h>usingnamespacestd;constintMAX=1050;intn,ans;intf[MAX],......
  • DP总结
    DP总结1.背包DP-0/1背包-完全背包-多重背包-分组背包-依赖背包-二维背包-树形背包DP0/1背包朴素版点击查看代码#include<bits/stdc++.h>usingnamespacestd;constintmaxn=1010;//f[i][j]表示前i个物品,体积不超过j时的最大价值//不选第i个物品时,f[i][j]......