「学习笔记」DP学习笔记 3

背包DP#

0-1背包#

给你 n 个物品和一个容量为 W 的背包,每个物品有自己的价值 v 和 需要占用的空间 c,求背包中的物品的所占用的空间不超过容量的最大价值。

特点:每个物品只能选一次

设置状态:f(i,j) 意味着前 i 个物品,容量为 j 的最大价值。

对于第 i 个物品,有选和不选两种情况,由此可以得到状态转移方程:

f(i,j)=max{f(i1,j),f(i1,jci)+wi}

由于二维空间有时会 MLE,同时我们发现对于 fi,只有 fi1 会对它有贡献,因此我们可以使用滚动数组优化,将状态转移方程优化为:

fj=max{fj,fjci+wi}

写成该转移方程式,则枚举容量要从大到小枚举,因为在 fj 更新时,fjci 是不能被更新的,以免将 f(i1,jci) 的信息覆盖掉。

例题:

[NOIP2005 普及组] 采药

山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。在给定的时间里,你要让采到的草药的总价值最大。

0-1 背包的模板题,很适合入门。

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

template<typename T>
inline T read() {
	T x = 0;
	bool fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? -x : x;
}

const int N = 110;

int W, n;
int c[N], v[N], f[1010];

int main() {
	W = read<int>(), n = read<int>();
	for (int i = 1; i <= n; ++ i) {
		c[i] = read<int>(), v[i] = read<int>();
	}
	for (int i = 1; i <= n; ++ i) {
		for (int j = W; j >= c[i]; -- j) {
			f[j] = max(f[j], f[j - c[i]] + v[i]);
		}
	}
	printf("%d\n", f[W]);
	return 0;
}

完全背包#

与 0-1 背包类似,但不同的地方在于,0-1 背包中每种物品只能选一次,而完全背包中每种物品可以选无数次。

设置状态:f(i,j) 意味着前 i 个物品,容量为 j 的最大价值。

转移方程如下:

f(i,j)=maxk=0+{f(i1,j),f(i1,jk×ci)+vi×k}

我们做一个简单的优化,对于 f(i,j),只要通过 f(i,jci) 转移就行了,因此转移方程为:

f(i,j)=max{f(i1,j),f(i,jci)+vi}

为什么是对的呢,我们可以这样想,f(i,jci) 已经由 f(i,j2×ci) 更新过了,所以 f(i,jci) 肯定是考虑了第 i 件物品的选择次数和空间的最优结果了,相当于最优子结构,我们可以利用局部最优子结构来优化枚举的复杂度。

同样,完全背包也可以将状态从二维化简为一维,转移方程式如下:

fj=max{fj,fjci+vi}

枚举顺序为正序枚举。

P1616 疯狂的采药

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

template<typename T>
inline T read() {
	T x = 0;
	bool fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? -x : x;
}

const int N = 1e4 + 5;
const int M = 1e7 + 5;

int t, n;
int a[N], b[N];
ll f[M];

int main() {
	t = read<int>(), n = read<int>();
	for (int i = 1; i <= n; ++ i) {
		a[i] = read<int>(), b[i] = read<int>();
	}
	for (int i = 1; i <= n; ++ i) {
		for (int j = a[i]; j <= t; ++ j) {
			f[j] = max(f[j], f[j - a[i]] + b[i]);
		}
	}
	printf("%lld\n", f[t]);
	return 0;
}

多重背包#

与 0-1 背包的不同在于每个物品有 k 个。

f(i,j)=maxk=0ki{f(i1,jk×ci)+vi×k}

优化:二进制拆分

可以使用二进制分组来时拆分方式更优美。

代码来自 OI-Wiki

index = 0;
for (int i = 1; i <= m; i++) {
  int c = 1, p, h, k;
  cin >> p >> h >> k;
  while (k > c) {
    k -= c;
    list[++index].w = c * p;
    list[index].v = c * h;
    c *= 2;
  }
  list[++index].w = p * k;
  list[index].v = h * k;
}

混合背包#

混合背包就是前三种背包混合在一起,即有些物品只能取一次,有些能取无数次,有些能取 k 次。

P1833 樱花

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

template<typename T>
inline T read() {
	T x = 0;
	bool fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? -x : x;
}

const int N = 1e4 + 5;

using tii = tuple<int, int>;

int n, t;
int c[N], v[N], p[N];
ll f[N];
char s[N];

int main() {
	int begh, begm, endh, endm;
	scanf("%s", s);
	sscanf(s, "%d:%d", &begh, &begm);
	scanf("%s", s);
	sscanf(s, "%d:%d", &endh, &endm);
	t = (endm - begm + 60) % 60;
	if (endm < begm) {
		t += (endh - begh - 1) * 60;
	} else {
		t += (endh - begh) * 60;
	}
	n = read<int>();
	for (int i = 1; i <= n; ++ i) {
		c[i] = read<int>(), v[i] = read<int>(), p[i] = read<int>(); 
	}
	for (int i = 1; i <= n; ++ i) {
		if (p[i] == 1) {
			for (int j = t; j >= c[i]; -- j) {
				f[j] = max(f[j], f[j - c[i]] + v[i]);
			}
		} else if (p[i] == 0) {
			for (int j = c[i]; j <= t; ++ j) {
				f[j] = max(f[j], f[j - c[i]] + v[i]);
			}
		} else {
			vector<tii> tmp;
			for (int g = 1; p[i] > g; g <<= 1) {
				p[i] -= g;
				tmp.emplace_back(g * c[i], g * v[i]);
			}
			if (p[i]) {
				tmp.emplace_back(p[i] * c[i], p[i] * v[i]);
			}
			int C, V;
			for (tii it : tmp) {
				tie(C, V) = it;
				for (int j = t; j >= C; -- j) {
					f[j] = max(f[j], f[j - C] + V);
				}
			}
		}
	}
	printf("%lld\n", f[t]);
	return 0;
}

二维费用背包#

与 0-1 背包相比,二维费用背包在选择物品时还要考虑费用,只需要在多一层循环来枚举费用即可,这里再开一维空间来存放物品编号就很容易 MLE 了。

P1855 榨取kkksc03

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

template<typename T>
inline T read() {
	T x = 0;
	bool fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? -x : x;
}

const int N = 110;

int n, m, t;
int mo[N], ti[N];
ll f[N << 1][N << 1];

int main() {
	n = read<int>(), m = read<int>(), t = read<int>();
	for (int i = 1; i <= n; ++ i) {
		mo[i] = read<int>(), ti[i] = read<int>();
	}
	for (int i = 1; i <= n; ++ i) {
		for (int j = m; j >= mo[i]; -- j) {
			for (int k = t; k >= ti[i]; -- k) {
				f[j][k] = max(f[j][k], f[j - mo[i]][k - ti[i]] + 1);
			}
		}
	}
	printf("%lld\n", f[m][t]);
	return 0;
}

分组背包#

与 0-1 背包相比,就是在当前组中只能选择一个,然后求最大价值。

对每一组都进行 0-1 背包即可。

P1757 通天之分组背包

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

template<typename T>
inline T read() {
	T x = 0;
	bool fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? -x : x;
}

const int N = 1100;

using tii = tuple<int, int>;

int n, m, lim;
ll f[N];
vector<tii> g[N]; 

int main() {
	m = read<int>(), n = read<int>();
	for (int i = 1, a, b, c; i <= n; ++ i) {
		a = read<int>(), b = read<int>(), c = read<int>();
		g[c].emplace_back(a, b);
		lim = max(lim, c);
	}
	for (int i = 1; i <= lim; ++ i) {
		for (int j = m; j >= 0; -- j) {
			for (tii it : g[i]) {
				if (j >= get<0>(it)) {
					f[j] = max(f[j], f[j - get<0>(it)] + get<1>(it));
				}
			}
		}
	}
	printf("%lld\n", f[m]);
	return 0;
}

有依赖的背包#

将主件与复件分类讨论,变化成分组背包即可。

P1064 [NOIP2006 提高组] 金明的预算方案

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

template<typename T>
inline T read() {
	T x = 0;
	bool fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? -x : x;
}

const int N = 5e4 + 5;

using tii = tuple<int, int>;

int n, m;
ll sumv, sump;
ll v[N], p[N], q[N];
ll f[N];
vector<tii> s[N], z[N];

void dfs(int i, int u) {
	if (u == (int)s[i].size()) {
		z[i].emplace_back(v[i] + sumv, p[i] * v[i] + sump);
		return ;
	}
	sumv += get<0>(s[i][u]);
	sump += get<1>(s[i][u]) * get<0>(s[i][u]);
	dfs(i, u + 1);
	sumv -= get<0>(s[i][u]);
	sump -= get<1>(s[i][u]) * get<0>(s[i][u]);
	dfs(i, u + 1);
}

int main() {
	n = read<int>(), m = read<int>();
	for (int i = 1; i <= m; ++ i) {
		v[i] = read<int>(), p[i] = read<int>(), q[i] = read<int>();
		s[q[i]].emplace_back(v[i], p[i]);
	}
	for (int i = 1; i <= m; ++ i) {
		if (q[i] == 0) {
			dfs(i, 0);
		}
	}
	for (int i = 1; i <= m; ++ i) {
		for (int j = n; j >= 0; -- j) {
			int V, P;
			for (tii it : z[i]) {
				tie(V, P) = it;
				if (V <= j) {
					f[j] = max(f[j], f[j - V] + P);
				}
			}
		}
	}
	printf("%lld\n", f[n]);
	return 0;
}

作者:yifan0305

出处:https://www.cnblogs.com/yifan0305/p/17806295.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

转载时还请标明出处哟!

posted @   yi_fan0305  阅读(15)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示