动态规划(一)

1.01背包

N 件物品和一个容量是 V 的背包。每件物品只能使用一次

i 件物品的体积是 vi,价值是 wi

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

思维导图:

状态表示:f[i][j] 表示从前 i 个物品中选出了总体积为 j 的物品放入背包,物品的最大价值和。

状态划分:其实是集合划分。

对于第 i 个物品,有两种策略,一种是选它,一种是不选它。所以我们可以将 f[i][j] 这个集合划分成两个集合,其中,不含 i 的集合就相当于 f[i1][j],含 i 的集合就相当于 f[i1][jv[i]]+w[i] (先将第 i 个物品从背包中拿出来,再放回去,最大值仍是最大值)。

综上所述,状态转移方程为 f[i][j]=max(f[i1][j],f[i1][jv[i]]+w[i])

代码如下:

#include <iostream>

using namespace std;

const int N = 1010;
int n, m;
int v[N], w[N], f[N][N];

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 - 1][j], f[i - 1][j - v[i]] + w[i]);
		}
	}
	printf("%d", f[n][m]);
	return 0;
} 

通过状态转移方程,我们发现,每一阶段 i 的状态只与上一阶段 i1 的状态有关。同时 jv[i]j 都是小于等于 j 的,所以可以用滚动数组的优化方式,将 f 数组优化成一维。

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]);
		}
	}

注意:由于 jv[i] 严格小于 j 所以在每层循环中会先被计算,这样就会导致在计算 f[i][j] 时调用的 f[i1][jv[i]] 已经被更新成了 f[i][jv[i]] ,所以内层循环要从大到小。

2.完全背包

N 种物品和一个容量是 V 的背包,每种物品都有无限件可用

i 种物品的体积是 vi,价值是 wi

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

思维导图:

集合划分:### 思维导图:

其实本质上还是可以用01背包的思想。

整理一下,得出状态转移方程:f[i][j]=max(f[i1][j],f[i1][jv[i]k]+w[i]k)

代码如下:

#include <iostream>
using namespace std;

const int N = 1010;
int n, m;
int v[N], w[N], f[N][N];

int main() {
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) {
        scanf("%d%d", &v[i], &w[i]);
    }
    for(register int i = 1; i <= n; i++) {
        for(register int j = 0; j <= m; j++) {
            for(register int k = 0; k * v[i] <= j; k++) {
                f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + w[i] * k);
            }
        }
    }
    printf("%d", f[n][m]);
    return 0;
}

但是我们会发现,这个代码的时间和空间复杂度太高,为 O(nms),我们来考虑优化。

将这个式子展开,会得到:

而展开之后可以发现,两个式子的中间项竟如此相似,而且上式就比下式多加了 w ,所以框起来的最大值也比下式多了 w

综上所述,动态转移方程可以简化为:f[i][j]=max(f[i1][j],f[i][jv[i]]+w[i])

再像01背包一样去掉一维就得到:
f[j]=max(f[j],f[jv[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]);
        }
    }

注意:完全背包和01背包的区别:

由于完全背包调用的是 f[i][jv[i]], 所以应该正序循环!

3.多重背包

N 种物品和一个容量是 V 的背包。

i 种物品最多有 si,每件体积是 vi,价值是 wi

求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。

思维导图:

多重背包的朴素版本其实和完全背包极其相似,它的状态转移方程为:
f[i][j]=max(f[i1][j],f[i1][jv[i]s[i]]+w[i]s[i])

同样的,我们来考虑优化。

通过尝试可以发现,优化完全背包的方法在这里行不通,因为多了一项。

那怎么办呢?

二进制优化法

由于一个一个地装太慢了,于是可以采用二进制拼凑的方法来拼出所有的可能。
众所周知,从 20,21,22,,2k1k2 的整数次幂中选出若干个数相加,可以表示出 02k1 之间的任何整数。我们求出满足 20+21+22++2p 的最大整数 p,设 Ri=Si20+21+22++2p+1,那么:

由于 20,21,22,2p 能拼凑出 02p+11 中的所有数,所以 20,21,22,2p,Ri 能拼凑出 Ri2p+1+Ri1 的所有数,即 RiSi 的所有数。又因为 Ri<2p+1 ,所以两个区间可以合并变成 0Si,综上所述,我们可以将数量为 Si 的第 i 个物品拆分成 p+2 个物品,它们的体积分别为:20Vi,21Vi,,2pVi2p+1Vi

这相当于对这每一份物品进行一次01背包就行了,时间复杂度降到 O(nmlogs)

代码如下:

#include <iostream>

using namespace std;

const int N = 2010, M = 25000;
int dp[N], v[M], w[M];
int n, m;

int main() {
    scanf("%d%d", &n ,&m);
    int cnt = 0;
    for(int i = 1; i <= n; i++) {
        int a, b, s;
        scanf("%d%d%d", &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;
        }
    }
    
    n = cnt;
    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]);
        }
    }
    printf("%d\n", dp[m]);
}

单调队列优化法

…………

4.分组背包

思维导图:

其实本质上还是可以用01背包的思想。

代码如下:

#include <iostream>
using namespace std;

const int N = 110;
int v[N][N], w[N][N];
int dp[N];
int s[N];
int n, m;

int main() {
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) {
        scanf("%d", &s[i]);
        for(int j = 0; j < s[i]; j++) {
            scanf("%d%d", &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) dp[j] = max(dp[j], dp[j - v[i][k]] + w[i][k]);
            }
        }
    }
    printf("%d\n", dp[m]);
}
posted @   Brilliant11001  阅读(7)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示