DP 重修笔记

DP 重修笔记

背包DP

其实大多的背包问题都是转换成 \(01\) 背包再优化复杂度的。

  • \(01\) 背包问题

就是物品只有选和不选两种选择,对应着 \(0, 1\) 关系,所以叫 \(01\) 背包。

我们设 \(f[i][j]\) 表示前 \(i\) 个物品,容量为 \(j\) 的背包最多能装下的价值。

\(i\) 是由 \(i - 1\) 转移来的,对于第 \(i\) 个物品,只有选和不选两种可能,不选的话,就等于 \(f[i - 1][j]\), 选的话,就是由 \(f[i - 1][j - v[i]] + w[i]\) 转移来的。

这样的话空间和时间复杂度为 \(\mathcal{O}(nk)\)

但是我们仔细观察,第 \(i\) 次抉择只与 \(i - 1\) 次抉择有关系,所以我们可以将第一位滚动掉。

但是我们要保证你用 \(f[j], f[j - v[i]] + w[i]\) 来更新的时候,存的是 \(i - 1\) 时更新的,所以我们要保证在枚举他的时候,\(j - v[i]\) 还未被更新过,所以我们要倒叙枚举他,来保证 \(f[j - v[i]]\)\(i - 1\) 的。

	for(int i = 1; i <= n; i++)
    	  for(int j = V; j >= v[i]; j--)
  				f[j] = max(f[j], f[j - v[i]] + w[i]);    

  • 多维费用背包

就套循环就行了,拿二维举个例子。

\[f[i][j][k] = \max(f[i-1][j][k], f[i -1][j-m[i]][k-v[i]]) \]

	for(int i = 1; i <= n; i++) {
		for(int j = V; j >= v[i]; j--) {
			for(int k = M; k >= m[i]; k--) {
				f[j][k] = max(f[j][k], f[j - v[i]][k - m[i]] + w[i]);
			}
		}
	}
  • 完全背包问题

当你改变了枚举顺序,\(f[i][j]\)\(v[i] \le j\) 的情况下 \(f[i][j]\) 会被 \(f[i - 1][j - w[i]]\) 多次更新,这相当于将物品 \(i\) 多次放入背包。

	for(int i = 1; i <= n; i++)
    	  for(int j = v[i]; j <= V; j++)
  				f[j] = max(f[j], f[j - v[i]] + w[i]);    

  • 多重背包问题:

就是每个物品有 \(k_i\) 种。

我们可以把他看成 \(k_i\) 个不同的物品,每个物品可以选一遍。

于是就转换成了 \(01\) 背包问题。

复杂度 \(\mathcal{O}{} (W { \sum_{i=1}^{n}}k_i)\)

这样的复杂度还不是太优。我们考虑优化分组的过程。我们知道一个数一定可以由 \(2\) 的幂次表示。

于是我们用二进制来优化。

	int n = read(), V = read();
	for(int i = 1 ; i <= n; i++) {
		int v = read(), w = read(), k = read();
		int c = 1;
		while(k - c >= 0) {
			k -= c;
			a[++idx].v = c * v;
			a[idx].w = c * w;
		} 
		a[++idx].v = v * k;
		a[idx].w = w * k;
	}

这样分完后再来做 \(01\) 背包就行。

复杂度为 \(\mathcal{O}(W \sum_{i=1}^{n} log_2k_i)\)

线性DP

  • 最长公共子序列 和 最大上升子序列

其实是一类问题,这里只讲 \(\mathcal{O}(n \log n)\) 的做法。

我们设 \(dp_i\) 表示长度为 \(i\) 的最长上升子序列的末端元素大小。

显然 \(dp[1] = a[1]\)

如果 \(a_i > dp[len]\) 显然可以直接接在后面 \(dp[++len] = a[i]\)

否则就找一个位置将他替换掉,找到第一个比他大的数去调换掉,这样肯定更优。

int n, a[MAXN], b[MAXN];
int rk[MAXN], dp[MAXN], len;

signed main() 
{
	n = read();
	for(int i = 1; i <= n; i++) a[i] = read(), rk[a[i]] = i;
	for(int i = 1; i <= n; i++) b[i] = read(), b[i] = rk[b[i]];
	dp[1] = b[1], len = 1;
	for(int i = 2; i <= n; i++) {
		if(b[i] >= dp[len]) dp[++len] = b[i];
		else dp[upper_bound(dp + 1, dp + len + 1, b[i]) - dp] = b[i];
	}
	return print(len), 0;
}

LCS 很简单, LIS 也很简单,那 LCIS 呢?

也很简单。

int n, m, a[N], b[N];
int f[1000][1000], pre[1000][1000];
int ANS[N], cnt = 0;

signed main() 
{
	n = read();
	for(int i = 1; i <= n; i++) a[i] = read();
	m = read();
	for(int i = 1; i <= m; i++) b[i] = read();
	a[0] = b[0] = -INF;
	for(int i = 1; i <= n; i++) {
		int Max = 0, pos = 0;
		for(int j = 1; j <= m; j++) {
			f[i][j] = f[i - 1][j];
			pre[i][j] = j;
			if(a[i] == b[j]) {
				if(f[i][j] < Max + 1) f[i][j] = Max + 1, pre[i][j] = pos;
			}
			if(a[i] > b[j]) {
				if(f[i - 1][j] > Max) Max = f[i - 1][j], pos = j;
			}		
		}
	}	
	int ans = 0, wz = 0;
	for(int i = 1; i <= m; i++) {
		if(f[n][i] > ans) ans = f[n][i], wz = i;
	}
	printf("%d\n", ans);
	int i = n, j = wz;
	while(i >= 1 && j >= 1) {
//		cout << i << " " << j << "\n";
		if(pre[i][j] != j) ANS[++cnt] = b[j];
		j = pre[i][j], i--;
	}	
	for(int i = cnt; i >= 1; i--) printf("%d ", ANS[i]);
	return 0;
}

区间 dp

posted @ 2022-07-01 22:26  TLE_Automation  阅读(38)  评论(4编辑  收藏  举报