01背包的一些感悟

一、朴素的爆搜

01背包的普通爆搜版

//输入
4 5
2 3
1 2
3 4
2 2
//输出
7

每种情况选或不选。这里给出没看书前自己写的。

#include <iostream>
#include <stdio.h>
#include <vector>
void init() {
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
}

using namespace std;
typedef pair<int, int> PII;
const int N = 1020;
PII a[N];

int maxn = 0;
int n, k;
void dfs(int u, int sum, int v) { //下标 总价值 总体积
	if (u == n)
	{
		if(v <= k)
			maxn = max(maxn, sum);
		return;
	}
	dfs(u + 1, sum + a[u].second, v + a[u].first);
	dfs(u + 1, sum, v);
}

int main()
{
	init();
	cin >> n >> k;
	for (int i = 0; i < n; i++) cin >> a[i].first >> a[i].second; //weight val
	
	dfs(0, 0, 0);
	cout << maxn;
	return 0;
}

这是一点儿优化也没有的搜索,运行过程中极有可能会出现重复计算。

想到用数组记录状态。我一直认为dp能把数组的状态想对题目就做出来了一半。

按照书上的思路再写一遍dfs,这次的dfs减少了一个参数。将递归函数返回一个值。

依然是没有优化的爆搜,为之后的数组表示状态做准备。

#include <iostream>
#include <stdio.h>
#include <vector>
void init() {
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
}

using namespace std;
const int N = 1020;
int w[N], v[N];

int maxn = 0;
int n, k;
int dfs(int u, int j) { //下标 剩余体积
	int res;
	if (u == n) { //搜不下去了
		res = 0;
	}
	else if (j < w[u]) { //放不下当前的
		res = dfs(u + 1, j);
	}
	else { //放得下当前的 两种情况都试下
		res = max(dfs(u + 1, j), dfs(u + 1, j - w[u]) + v[u]);
	}
	return res;
}

int main()
{
	init();
	cin >> n >> k;
	for (int i = 0; i < n; i++) cin >> w[i] >> v[i]; //weight val
	
	cout << dfs(0, k);
	return 0;
}

二、记忆化搜索

然后我们加上记忆化数组dfs搜索。啥是记忆化数组?用数组记录某个状态、当程序需要用到这个状态时:如果有直接返回数字;如果没有更新。某种意义上也是拿空间换时间的一种策略。

这里的dfs参数有两个:下标和剩余体积。有没有可能用到之前出现过的状态呢?答案是有,因为dfs有回溯

用空间换时间后的代码如下(其实就是稍加改进,联想斐波那契数列的数组推导):

#include <iostream>
#include <stdio.h>
#include <vector>
#include <cstring>

void init() {
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
}

using namespace std;
const int N = 1020;
int w[N], v[N]; //体积 价值
int dp[N][N]; //用空间换时间

int maxn = 0;
int n, k;
int dfs(int u, int j) { //下标 剩余体积
	if (dp[u][j] > 0) return dp[u][j];
	int res;
	if (u == n) { //搜不下去了
		res = 0;
	}
	else if (j < w[u]) { //放不下当前的
		res = dfs(u + 1, j);
	}
	else { //放得下当前的 两种情况都试下
		res = max(dfs(u + 1, j), dfs(u + 1, j - w[u]) + v[u]);
	}
	return dp[u][j] = res;
}

int main()
{
	init();
	memset(dp, -1, sizeof dp);
	cin >> n >> k;
	for (int i = 0; i < n; i++) cin >> w[i] >> v[i]; //weight val
	
	cout << dfs(0, k);
	return 0;
}

改进版本的核心是数组表示状态。

用这个方式来表示下:令dp[i][j] 表示 从第 i 个物品开始挑选、总重量小于等于 j 时,总价值的最大值

然后我们写下代码:

#include <iostream>
#include <stdio.h>
#include <vector>
#include <cstring>

void init() {
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
}

using namespace std;
const int N = 1020;
int w[N], v[N];
int dp[N][N]; //用空间换时间

int maxn = 0;
int n, k;

//dp[i][j] 表示 从第i个物品开始挑选、总重量小于等于j时,总价值的最大值
void solve() {
	for (int i = n - 1; i >= 0; i--) {
		for (int j = 0; j <= k; j++) {
			if (j < w[i]) {
				dp[i][j] = dp[i + 1][j];
			}
			else {
				dp[i][j] = max(dp[i + 1][j], dp[i + 1][j - w[i]] + v[i]);
			}
			//if (dp[i][j] > 0) cout << i << ' ' << j << endl;
		}
	}
}

int main()
{
	init();
	cin >> n >> k;
	for (int i = 0; i < n; i++) cin >> w[i] >> v[i]; //重量 价值
	solve();
	cout << dp[0][k];
	return 0;
}

在上一个程序中我们逆着表示的。改变状态表示意义尝试下:令dp[i][j] 表示 挑选到第 i-1 个物品、总重量小于等于 j 时,总价值的最大值。

自己试着实现了下,用的偏移量防止数组访问下标 -1

#include <iostream>
#include <stdio.h>
#include <vector>
#include <cstring>

void init() {
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
}

using namespace std;
const int N = 1020;
int w[N], v[N];
int dp[N][N]; //用空间换时间

int maxn = 0;
int n, k;

//dp[i][j] 表示 挑选到第 i-1 个物品、总重量小于等于 j 时,总价值的最大值
void solve() {
	for (int i = 1; i <= n; i++) {
		for (int j = 0; j <= k; j++) {
			if (j < w[i-1]) { //i-1偏移量
				dp[i][j] = dp[i - 1][j];
			}
			else {
				dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i-1]] + v[i-1]); //i-1是偏移量
			}
		}
	}
}

int main()
{
	init();
	cin >> n >> k;
	for (int i = 0; i < n; i++) cin >> w[i] >> v[i]; //重量 价值
	solve();
	cout << dp[n][k];
	return 0;
}

同样的状态表示换一个推导方程:

#include <iostream>
#include <stdio.h>
#include <vector>
#include <cstring>

void init() {
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
}

using namespace std;
const int N = 1020;
int w[N], v[N];
int dp[N][N]; //用空间换时间

int maxn = 0;
int n, k;

//dp[i+1][j] 表示 选到 i, 总体积不超过 j
void solve() {
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j <= k; j++) {
			if (j < w[i]) {
				dp[i + 1][j] = dp[i][j];
			}
			else {
				dp[i + 1][j] = max(dp[i][j], dp[i][j - w[i]] + v[i]);
			}
		}
	}
}

int main()
{
	init();
	cin >> n >> k;
	for (int i = 0; i < n; i++) cin >> w[i] >> v[i]; //重量 价值
	solve();
	cout << dp[n][k];
	return 0;
}

三、实例

acwing周赛T3

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
typedef long long LL;
const int N = 220;
LL dp[N][N]; //dp[i+1][j]表示 选择i, 一共选了j 偏移一位
LL a[N];

int main()
{
    memset(dp, -0x3f, sizeof dp);
    int n, k, x; cin >> n >> k >> x; //k是窗口 选x个
    for (int i = 1; i <= n; i ++ ) cin >> a[i];
    
    dp[0][0] = 0;
    for (int i = 1; i <= n; i ++){
        for(int j = 1; j <= x; j ++){ //x是次数
            //再搞一个向前搜索
            for(int m = max(0, i - k); m < i; m ++){
                dp[i][j] = max(dp[i][j], dp[m][j-1] + a[i]);
            }
        }
    }
    
    LL res = -1;
    for(int i = n - k + 1; i <= n; i ++) res = max(res, dp[i][x]);
    cout << res;
    
    return 0;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值