首页 > 其他分享 >dp总结(背包,线性,区间,坐标,树形)

dp总结(背包,线性,区间,坐标,树形)

时间:2024-02-17 16:22:20浏览次数:25  
标签:std 背包 int max 树形 maxn 物品 dp

背包dp

0/1背包

  • 这种背包会提供可选的物品,背包的容积以及每件物品的价值,并且在选择物品是每件物品只有选一件或不选两种状态。
  • 例题

    输入
    4 5
    1 2
    2 4
    3 4
    4 5
    输出
    8
二维解法代码
//状态转移方程为:f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i])

#include"bits/stdc++.h"
using namespace std;

const int maxn=1010;
const int maxw=1010;

//f[i][j]表示前i件物品占j空间的最大价值
//v[i]表示第i件物品所占的体积
//w[i]表示选第i件物品可以创造的价值
int f[maxn][maxn],v[maxn],w[maxn];
int n,m;

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];
            //若不选第i件物品,前i件物品创造的价值等于前i-1件物品创造的价值
			if(j>=v[i])
            //保证背包剩余的容量可以装下第i件物品
			{
				f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
                //若选第i件物品,比较不选i和选i所创造的价值,f[i][j]为两种情况中更优的一种
			}
		}
	}
	cout <<f[n][m];
    //输出前n件物品占m容量时的最大价值
	
	return 0;
}
  • 本题中背包现在的状态会存入i中,背包上一次的状态存在于i-1中,而现在的状态只与上一次的状态有关,所以可以用滚动数组将二维优化为一维
一维解法代码
//状态转移方程为:f[j]=max(f[j],f[j-v[i]]+w[i])

#include"bits/stdc++.h"
using namespace std;

const int maxn=1010;
const int maxw=1010;

////f[i][j]仍然表示前i件物品占j空间的最大价值,但i体现在第一层循环中
int f[maxw],v[maxn],w[maxn];
int n,m;

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];
	
	return 0;
}

完全背包

  • 在完全背包中,所提供的物品不仅有价值和所占容积,还会提供每种物品拥有的数量,也就是说,每种物品有选一件,选多件和不选三种状态,而选一件和选多件可以统一看成选k件
  • 例题
    设有n种物品,每种物品有一个重量及一个价值。但每种物品的数量是无限的,同时有一个背包,最大载重量为M,今从n种物品中选取若干件(同一种物品可以多次选取),使其重量的和小于等于M,而价值的和为最大。
    输入
    10 4
    2 1
    3 3
    4 5
    7 9
    输出
    max=12
二维解法代码
//状态转移方程与0/1背包很像,只是每种物品选择的数量由一件变为k件
//状态转移方程:f[i][j]=max(f[i-1][j-k*v[i]]+k*w[i],f[i][j])

#include"bits/stdc++.h"
using namespace std;

const int maxn=3000;

int f[maxn][maxn],v[maxn],w[maxn];
int n,m;

int main()
{
	cin >>m>>n;
	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++)
        //记录背包容积
		{
			for(int k=0;k*v[i]<=j;k++)
            //枚举每种物品选择的数量
			{
				f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
			}
		}
	}
	cout <<"max="<<f[n][m];
	
	return 0;
}
  • 与0/1背包相同,完全背包现在的状态i也只与上个状态i-1相关,所以也可以用滚动数组优化为一维
一维解法代码
//状态转移方程与0/1背包相似
//状态转移方程:f[j]=max(f[j-k*v[i]]+k*w[i],f[j])
//但我们可以将完全背包看成0/1背包,此时需要将每个物品看做一个选取对象,而不是一种物品,此时状态转移方程就变为0/1背包的状态转移方程
//状态转移方程为:f[j]=max(f[j],f[j-v[i]]+w[i])

#include"bits/stdc++.h"
using namespace std;

const int maxn=3000;

int f[maxn],v[maxn],w[maxn];
int n,m;

int main()
{
	cin >>m>>n;
	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++)
        //这层循环是完全背包和0/1背包不同的地方,正序遍历为了把每种物品中的每个拆开,从而使完全背包变为0/1背包(必须正序)
		{
			f[j]=max(f[j],f[j-v[i]]+w[i]);
		}
	}
	cout <<"max="<<f[m];
	
	return 0;
}

多重背包

  • 多重背包与完全背包相似,但多重背包每种物品的个数是有限的(既k∈s[i])
  • 例题

    输入
    4 5
    1 2 3
    2 4 1
    3 4 3
    4 5 2
    输出
    10
转0/1背包解法代码
//通过遍历的物品的顺序已经将多重背包转化为0/1背包,所以状态转移方程和0/1相同
//状态转移方程:f[j]=max(f[j],f[j-v[i]]+w[i])

#include <bits/stdc++.h>
using namespace std;

const int maxn=110;

int n,m;	 
int f[maxn];	 
int v[maxn],w[maxn],s[maxn];	 

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=1;j<=s[i];j++)//i,j两层循环将每种物品的每一个拆分开,形成0/1背包
		{
			for(int k=m;k>=v[i];k--)
			{
				f[k]=max(f[k],f[k-v[i]]+w[i]);
			}			
		}
	}
	cout <<f[m];

	return 0;
}
  • 直接将多重背包转换为0/1背包的复杂度很大,容易被卡时间,所以可以用二进制拆分进行优化

二进制拆分

  • 原理
    任何数字都可以表示成若干个2ⁿ的和(eg:7=2º+2¹+2²),因为任何整数都可以转成二进制,二进制就是若干个“1”(2的幂数)的和,所以我们可以将物品i拆成体积为v[i]×2ⁿ,价值为w[i]×2ⁿ的若干个物品
二进制拆分解法代码
//已经通过二进制拆分将物品拆成0/1背包,所以状态转移方程与0/1背包相同
//状态转移方程为:f[j]=max(f[j],f[j-v[i]]+w[i])

#include <bits/stdc++.h>
using namespace std;

const int maxn=15000;
const int maxm=2010;

int n,m;	 
int f[maxm];	 
int v[maxn],w[maxn],s[maxn],cnt;	 

int main()
{	
	int vi,wi,si;
	cin >>n>>m;

	//二进制拆分
	for(int i=1; i<=n; i++)
	{
		cin >>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]);
		}	
	}
	cout <<f[m];

	return 0;
}

   

分组背包

  • 分组背包中物品和0/1背包相同,都是以个位单位,但是分组背包中的物品会划分到不同的组中,并且题中会出现与组相关的限制条件
  • 例题
    一个旅行者有一个最多能用V公斤的背包,现在有n件物品,它们的重量分别是W1,W2,...,Wn,它们的价值分别为C1,C2,...,Cn。这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
    输入格式
    第一行:三个整数,V(背包容量,V<=200),N(物品数量,N<=30)和T(最大组号,T<=10);
    第2..N+1行:每行三个整数Wi,Ci,P,表示每个物品的重量,价值,所属组号。
    输出格式
    仅一行,一个数,表示最大总价值。
    输入
    10 6 3
    2 1 1
    3 3 1
    4 8 2
    6 9 2
    2 8 3
    3 9 3
    输出
    20
二维解法代码
//分组背包就是以组为单位进行0/1背包,所以状态转移方程和0/1背包相同
//状态转移方程::f[i][j]=max(f[i-1][j],f[i-1][j-v[g[i][k]]]+w[g[i][k]]

#include"bits/stdc++.h"
using namespace std;

const int maxn=2500;

int n,m,t;
int v[maxn],w[maxn];
int dp[15][maxn],g[15][maxn];
//dp[i][j]表示在背包容量为j对第i组的第k个物品的决策最大值
//g[i][j]表示第i组第j个物品的编号

int main()
{
    cin >>m>>n>>t;
    int x;
    for(int i=1;i<=n;i++)
    {
        cin >>v[i]>>w[i]>>x;
        g[x][++g[x][0]]=i;
    }
    //以组为单位进行0/1背包
    for(int i=1;i<=t;i++)
    {
        //0/1背包
        for(int j=0;j<=m;j++)
        {
            dp[i][j]=dp[i-1][j];
            for(int k=1;k<=g[i][0];k++)
            {
                if(j>=v[g[i][k]])
                {
                    x=g[i][k];
                    dp[i][j]=max(dp[i][j],dp[i-1][j-v[x]]+w[x]);
                }
            }
        }
    }
    cout <<dp[t][m];

    return 0;
}
  • 在分组背包中,当前组i所产生的最大价值只与上一组i-1所产生的最大价值有关,所以可以用滚动数组优化
一维解法代码
//分组背包就是以组为单位进行0/1背包,所以状态转移方程和0/1背包相同
//状态转移方程为:f[j]=max(f[j],f[j-v[i]]+w[i])

#include"bits/stdc++.h"
using namespace std;

const int maxn=2500;

int n,m,t;
int v[maxn],w[maxn];
int dp[maxn],g[15][maxn];

int main()
{
    cin >>m>>n>>t;
    int x;
    for(int i=1;i<=n;i++)
    {
        cin >>v[i]>>w[i]>>x;
        g[x][++g[x][0]]=i;
    }
    //以组为单位进行0/1背包
    for(int i=1;i<=t;i++)
    {
        //0/1背包(与一维0/1背包相同,需要倒序遍历)
        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];
                    dp[j]=max(dp[j],dp[j-v[x]]+w[x]);
                }
            }
        }
    }
    cout <<dp[m];

    return 0;
}

二维费用背包

  • 顾名思义,这种背包需要开二维的dp(至少目前没见过用一维的),这种背包和其他背包不同的是,它不只有体积这一个限制,还有质量的限制(当然体积和质量用别的也可以平替),所以我们在遍历时需要体找到积不超限制的同时质量也不超限制的最优方案
  • 例题
    航天飞机的体积有限,当然如果载过重的物品,燃料会浪费很多钱,每件食品都有各自的体积、质量以及所含卡路里,在告诉你体积和质量的最大值的情况下,请输出能达到的食品方案所含卡路里的最大值,当然每个食品只能使用一次.
    输入格式
    第一行两个数体积最大值(<400)和质量最大值(<400)
    第二行 一个数 食品总数N(<50).
    第三行-第3+N行
    每行三个数 体积(<400) 质量(<400) 所含卡路里(<500)
    输出格式
    一个数 所能达到的最大卡路里(int范围内)
    输入
    320 350
    4
    160 40 120
    80 110 240
    220 70 310
    40 400 22
    输出
    550
唯一会的一种解法代码
//因为有两个条件限制,所以状态转移方程中应出现两个条件
//状态转移方程:f[j][k]=max(f[j][k],f[j-v[i]][k-m[i]]+w[i])(i为物品的编号)

#include"bits/stdc++.h"
using namespace std;

const int maxn=10000;

//f[i][j]表示在体积不超过i且质量不超过j时的最优方案
int f[maxn][maxn],v[maxn],m[maxn],w[maxn];
int n,maxt,maxm;

int main()
{
	cin >>maxt>>maxm>>n;
	for(int i=1;i<=n;i++)
	{
		cin >>v[i]>>m[i]>>w[i];
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=maxt;j>=v[i];j--)
		{
			for(int k=maxm;k>=m[i];k--)
			{
				f[j][k]=max(f[j][k],f[j-v[i]][k-m[i]]+w[i]);
			}
		}
	}
	cout <<f[maxt][maxm];
	
	return 0;
}

标签:std,背包,int,max,树形,maxn,物品,dp
From: https://www.cnblogs.com/wang-qa/p/18017815

相关文章

  • 坐标dp
    坐标dp典型例题:传纸条题目描述小渊和小轩是好朋友也是同班同学,他们在一起总有谈不完的话题。一次素质拓展活动中,班上同学安排做成一个m行n列的矩阵,而小渊和小轩被安排在矩阵对角线的两端,因此,他们就无法直接交谈了。幸运的是,他们可以通过传纸条来进行交流。纸条要经由许多同学传......
  • 线性DP
    这篇主要涉及线性DP。先介绍给模型,求最长上升子序列。点击查看代码#include<bits/stdc++.h>usingnamespacestd;constintN=1020;intn;intf[N],ans,a[N];intpre[N],te;voidoutput(intx){ if(x==0) { return; } output(pre[x]); cout<<a[x]<<"";......
  • 区间dp
    区间dp区间dp属于线性dp的一种,以“区间长度”作为dp的“阶段”,用两个坐标(区间的左、右端点)描述每个维度。区间dp中,一个状态往往由若干个比它更小且包含于它的区间所代表的阶段转移而来,所以区间dp的决策往往就是划分dp的方法。典型例题:石子合并for(inti=1;i<=n;i++)f[i][i]=......
  • 五大基础dp
    动规条件•最优化原理:如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优化原理。•无后效性:即某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关。•有重叠子问题:即子问题之......
  • 背包dp
    01背包:所谓01背包,就是每种物体有一个价值且数量只有一个,给定一个背包容量,在不超出背包容量的前提下在n个物体中取m个,求最大价值。点击查看代码//f[i]指体积为i时的最大价值for(inti=1;i<=n;i++){//第一层循环是指遍历每种物体,n是物体种数 for(intj=m;j>=v[i];j--){//第......
  • 背包DP
    下面介绍一下背包DP主要题型基础模型(没什么好说的,上模板)1.01背包最主要的模板,应用很多,很重要!!!倒着遍历容积,不会受后选小的f[i][j]影响,已经选过的物品不会再选一遍。点击查看代码#include<bits/stdc++.h>usingnamespacestd;constintN=10020;intf[N];intn,m;intv......
  • 关于动态规划(Dynamic Programming)的若干思考 ------ [1.背包dp]
    背包dp;1.01背包(1)领域展开#include<bits/stdc++.h>//simpleusingnamespacestd;constintmaxm=2024;intn,m;intw[maxm],v[maxm],f[maxm][maxm];intmain(){ cin>>n>>m; for(inti=1;i<=n;i++) cin>>v[i]>>w[i]; for(i......
  • 背包dp
    1、01背包f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i])二维为背包现有容量,一维为前i个物品表示在前i个物品所能选取的最大价值在判断第i个的最大值时要由前一个的状态转移过来;即下一层的状态由上一层转移来;可以直接省掉第一维(压维),从后往前更新过来,若还是正序就会出现一种情况......
  • dp的优化(单队,斜率)
    1.单调队列优化\(dp\)维护最小值:\(x\leqslantq.tail()\)维护最大值:\(x\geqslantq.tail()\)其实原理不难,当\(dp\)的转移源头是一个区间时,往往使用单调队列来维护区间最值(一般队列里装下标以方便维护区间大小,但也只是一般情况),节省了处理区间的时间(甚至噶掉一维),重点是对区间的......
  • 树状DP心得
    说一下近日所学的主要两种题型,一个是分叉情况问题,一种是树上背包问题。分叉情况问题具体的题可以参考小胖守皇宫和三色二叉树。点击查看代码小胖守皇宫#include<bits/stdc++.h>usingnamespacestd;constintN=2000;vector<int>son[N];intfa[N],h[N],f[N][3];//f[0]......