背包问题总结

1. 0/1背包

n 个物品,每个物品有一个重量 v[i] 和一个价值 w[i]。背包的最大承重为 m。每个物品只能使用一次(0/1意味着要么不选这个物品,要么完全选这个物品)。

  • 自底向上与制表递推
int w[N], v[N]; // 价值、重量
int dp[N][N];

int solve(int n, int m) // n件物品、m总体积
{
	for (int i = 1; i <= n; i++)
	{
		for (int j = 0; j <= m; j++)
		{
			if (j < v[i])
				dp[i][j] = dp[i - 1][j];
			else
				dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v[i]] + w[i]);
		}
	}
	return dp[n][m];
}
  • 自顶向下与记忆化
int w[N], v[N]; // 价值、重量
int dp[N][N];

int solve(int i, int j)
{
	if (dp[i][j] != 0)
		return dp[i][j];
	if (i == 0)
		return 0;
	int res;
	if (j < v[i])
		res = solve(i - 1, j);
	else
		res = max(solve(i - 1, j), solve(i - 1, j - v[i]) + w[i]);
	return dp[i][j] = res;
}
  • 滚动数组

1.交替滚动

int dp[2][N];

int solve(int n, int m) // n件物品、m总重量
{
	int now = 0, old = 1;
	for (int i = 1; i <= n; i++)
	{
		swap(now, old);
		for (int j = 0; j <= m; j++)
		{
			if (j < v[i])
				dp[now][j] = dp[old][j];
			else
				dp[now][j] = max(dp[old][j], dp[old][j - v[i]] + w[i]);
		}
	}
	return dp[now][m];
}

2.自我滚动

int dp[N];

int solve(int n, int m) // n件物品、m总重量
{
	for (int i = 1; i <= n; i++)
	{
		for (int j = m; j >= v[i]; j--)
		{
			dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
		}
	}
	return dp[m];
}

2.分组背包

假设我们有n组物品,每组物品的数量为s[i],但在同一组内的物品冲突,因此每组最多只能选择一件物品,每组的物品有自己的重量v[i][j]和价值w[i][j]。我们需要从这些物品中选择一些放入背包中,使得背包中物品的总价值最大,同时不超过背包的最大承重m

分组背包问题就是在01背包问题的基础之上,多了一个在每个组中选出最优的那个物品(或者不选)。

中层循环是从背包容量m到0的逆序遍历,这是为了防止同一个组中的物品被重复放入背包(即保证每组中至多选择一个物品)

int w[N][N], v[N][N], s[N];	// w价值、v重量
int dp[N];
int n, m;

int solve(int n, int m) //n组物品、m总容量、每组物品s[i]个
{
	for (int i = 1; i <= n; i++)
	{
		for (int j = m; j >= 0; j--)
		{
			for (int k = 1; k <= s[i]; k++) // 遍历第i组中的每个物品
			{
				if (j >= v[i][k])
					dp[j] = max(dp[j], dp[j - v[i][k]] + w[i][k]);
			}
		}
	}
	return dp[m];
}

3.完全背包

完全背包问题是背包问题的一种变体。与0/1背包问题不同的是,在完全背包问题中,每种物品都有无限多个可用,这意味着你可以选择任意数量的同一种物品,只要不超过背包的容量限制

  • 朴素写法
int w[N], v[N]; // w价值、v重量
int dp[N][N];

int solve(int n, int m) //n组物品、m总容量
{
    for (int i = 1; i <= n; i++)
    {
        for (int j = 0; j <= m; j++)
        {
            for (int k = 0; k * v[i] <= j; k++)
            {
                dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i] * k] + w[i] * k);
            }
        }
    }
    return dp[n][m];
}
  • 优化一
int w[N], v[N]; // w价值、v重量
int dp[N][N];

int solve(int n, int m) // n组物品、m总容量
{
    for (int i = 1; i <= n; i++)
    {
        for (int j = 0; j <= m; j++)
        {
            if (j < v[i])
                dp[i][j] = dp[i - 1][j];
            else
                dp[i][j] = max(dp[i][j], dp[i][j - v[i]] + w[i]);
        }
    }
    return dp[n][m];
}
  • 优化二
int w[N], v[N]; // w价值、v重量
int dp[N];

int solve(int n, int m) // n组物品、m总容量
{
    for (int i = 1; i <= n; i++)
        for (int j = v[i]; j <= m; j++)
            dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
    return dp[m];
}

4.多重背包

多重背包问题(也称为有数量限制的背包问题)是背包问题的一个变种。在这种情况下,每种物品都有一个数量限制,即每种物品最多可以选取多少个。这介于0/1背包问题(每种物品只能选一次)和完全背包问题(每种物品可以无限次选取)之间。

问题定义

n组物品,每组物品有s[i]个,每种物品的重量为v[i]、价值为w[i]。需要从这些物品中选择一些放入背包中,使得背包中物品的总价值最大,同时不超过背包的最大承重m

1. 直接展开法

这种方法直接将每种物品的所有可能性展开,例如,如果某种物品的数量上限是5,则可以构造出5个虚拟物品(0个、1个、2个、3个、4个和5个),然后将问题转化为0/1背包问题。

2. 平滑化处理

这种方法通过对物品进行预处理,减少状态转移次数,从而提高效率。具体来说,可以将数量限制较大的物品进行平滑化处理,将其转换为几个子问题。

平滑化处理的步骤

  1. 预处理阶段:对于每种物品,如果数量上限较大,可以将其分解成多个子物品。
  2. 状态转移:利用预处理后的物品进行动态规划的状态转移。
  • 一般方法
int w[N], v[N], s[N]; // w价值、v重量
int dp[N][N];

int solve(int n, int m) // n组物品、m总容量
{
    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++)
            {
                dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i] * k] + w[i] * k);
            }
        }
    }
    return dp[n][m];
}
  • 滚动数组
int w[N], v[N], s[N]; // w价值、v重量
int dp[N];

int solve(int n, int m) // n组物品、m总容量
{
    for (int i = 1; i <= n; i++)
    {
        for (int j = m; j >=v[i]; j--)
        {
            for (int k = 0; k <= s[i] && k * v[i] <= j; k++)
            {
                dp[j] = max(dp[j], dp[j - v[i] * k] + w[i] * k);
            }
        }
    }
    return dp[m];
}
  • 二进制优化
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值