118 lines
6.7 KiB
Markdown
118 lines
6.7 KiB
Markdown
- 考虑状态和转移两种东西
|
||
- 可以把状态看作是DAG上的点,然后状态转移就是边
|
||
- LCS问题
|
||
- 两个序列`A[1..n]`和`B[1..m]`,求最长的子序列既是A的也是B的
|
||
- 状态的定义:$f(i,j)=A[1:i]和B[1:j]的LCS$
|
||
- 状态转移:如果`A[i]==B[j]`,那就直接接到后面`f[i,j] = f[i-1, j-1] + 1`;否则更新为`max(f[i-1, j], f[i, j-1])`
|
||
- LIS问题
|
||
- 求最长子序列,子序列满足某些有序限制
|
||
- 状态为:以第i个元素结尾的子序列的最长
|
||
- 状态转移:在前面的子序列里面找一个最长且满足要求的子序列接在后面,也就是$f[i] = \max_{1 \le j \lt i, w[i] \lt w[j]}(f[j]) + 1$
|
||
- 01背包
|
||
- n个物品,每个物品有价值v和重量w两种属性,W容量的包。每个物品是唯一的,求能装进包里的物品的价值最大。
|
||
- 状态:$f[i, c]$为前i个物品已经处理完成,容量为c情况下的最大价值
|
||
- 状态转移:当前物品装或者不装的决策。`f[i,c] = max(f[i-1, c], f[i-1, c-w[i]] + v[i])`。如果没有装物品,那么容量不变,价值也不变;如果装了第i个物品,那么它的价值增加且容量减小,因为这里当前状态`f[i,c]`是固定的(写的是一个逆推的形式),因此从更小容量转移过来
|
||
- 在具体做的时候,需要注意一下顺序和空间压缩。
|
||
- 因为第i个物品的i是有顺序的,因此可以压缩掉一维。变成这样`f[c] = max(f[c], f[c-w[i]] + v[i])`
|
||
- 在滚动压缩的时候,需要保证逆推的顺序,否则容量较小的情况更新过后会影响到后面的推导。不过在非滚动数组的情况下,这则是不需要的,因为反正用的是i-1的状态
|
||
```c
|
||
for (int i = 1; i <= n; ++ i)
|
||
for (int c = W; c >= w[i]; -- c)
|
||
f[c] = max(f[c], f[c - w[i]] + v[i])
|
||
```
|
||
- 完全背包
|
||
- n个物品,每个物品有价值v和重量w两种属性,W容量的包。**每个物品可以取多次**,求能装进包里的物品的价值最大。
|
||
- 状态是一样的,唯一的区别在于,完全背包需要枚举当前物品取的次数
|
||
- 不过这也可以优化掉,因为如果是正向推的话,已经包含了取所有可能次数的情况。
|
||
- **0-1背包和完全背包区别就在于容量维度的迭代顺序**
|
||
- ```c
|
||
for (int i = 1; i <= n; ++ i)
|
||
for (int c = w[i]; c <= W; ++ c)
|
||
f[c] = max(f[c], f[c - w[i]] + v[i]);
|
||
```
|
||
- 多重背包
|
||
- n种物品,每个物品有价值v和重量w两种属性,W容量的包。每种物品有k个,求能装进包里的物品的价值最大。
|
||
- 转化为01背包求解。因为每种物品k个就等于有k个属性相同的物品分别取或者不取。
|
||
- 二维背包
|
||
- 这道题是很明显的 0-1 背包问题,可是不同的是选一个物品会消耗两种价值(经费、时间),只需在状态中增加一维存放第二种价值即可,同时枚举的时候也变成了两个量。
|
||
- 也就是状态方程变成了`f[i, c, d] = f[i-1, c - c[i], d-d[i]] + v[i]`
|
||
- 背包状态数
|
||
- 把最大值换成求和,初始状态变成1(因为可以啥也不装)
|
||
- `f[i, c] += f[i-1, c-w[i]]`
|
||
- 背包方案
|
||
- 一种方案是,另外开一个数组记录状态`[i, c]`下有没有取i
|
||
```
|
||
int v = V; // 记录当前的存储空间
|
||
|
||
// 因为最后一件物品存储的是最终状态,所以从最后一件物品进行循环
|
||
for (从最后一件循环至第一件) {
|
||
if (g[i][v]) {
|
||
选了第 i 项物品;
|
||
v -= 第 i 项物品的重量;
|
||
} else {
|
||
未选第 i 项物品;
|
||
}
|
||
}
|
||
```
|
||
- 另一种方案则是,不优化空间,然后倒着推算更新点(如果状态和前一个物品不一样,那肯是发生了转移)
|
||
```
|
||
for (int i = n; i > 0; -- i) {
|
||
if (dp[i][res] != dp[i-1][res]) {
|
||
sel[i] = 1;
|
||
res -= w[i];
|
||
}
|
||
}
|
||
```
|
||
- 区间dp
|
||
- 具有明显的可划分性质
|
||
- 经典的合并石子题:有n个数排成一个环,进行n-1次合并操作,每次操作将相邻的两堆合并成一堆,能获得新的一堆中的石子数量的和的得分。你需要最大化你的得分。
|
||
- 状态:`f[l, r]`表示区间`[l:r]`合并所能得到的最大分数
|
||
- 方程:枚举合并点k,取最大值。$f[l, r] = max_{i\le k \lt j}(f[i, k] + f[k+1, j]) + sum(a[i : j])$
|
||
- 可以用前缀和来优化求和的过程
|
||
- 这里的阶段划分并不是自然的(LIS、LCS自然就是LTR或者RTL,背包也就是依次取或者不取一个物品),需要用区间长度进行划分。
|
||
- 对于该问题,还需要解决围成环这个问题,比较好的解决方法是展开成链式之后复制一遍,最后在n个长度为n的区间里面取最大值
|
||
- 实现如下
|
||
```c
|
||
for (int len = 1; len <= n; ++ len) {
|
||
for (int i = 1; i + len - 1 <= 2 * n; ++ i) { // 实际上是j<=2*n
|
||
int j = len + i - 1;
|
||
for (int k = i; k < j; ++ k) {
|
||
f[i][j] = max(f[i][j], f[i][k] + f[k+1][j] + sum[j] - sum[i-1]);
|
||
}
|
||
}
|
||
}
|
||
// ...
|
||
ans = [&]() {
|
||
int mx = 0;
|
||
for (int i = 1; i <= n; ++ i) mx = max(mx, f[i][i + n - 1]);
|
||
return mx;
|
||
}();
|
||
```
|
||
- 能量项链也是完全一样的思路,除了值的计算方法稍有区别。
|
||
- 树形dp
|
||
- 就是在树上做dp,一般而言,状态肯定有某一维是“以节点u为根的子树”这样的东西。然后遍历子树求解就好了。
|
||
- 例题1:POJ1463。有若干结点,结点之间有路相连,构成树形结构,如果在一个结点上放置一个士兵,与这个结点相连的路就可以被监视,现在要监视所有的路,问至少要多少士兵。
|
||
- 状态:$f[u, 0/1]$为节点u放置或者不放置士兵情况下的最小值
|
||
- 状态转移:如果当前节点不放,那么子节点就必须放置,也就是`f[u][0] = sum(f[v][1])`;如果当前节点放置,那么子节点可以放,也可以不放,对每个子节点的两种状态求最小值然后加起来(再加上自己1):`f[u][1] = sum(min(f[v][0],f[v][1]) + 1`。
|
||
- 舞会那个题的思路也是类似的,就不赘述了
|
||
- Floyd最短路
|
||
- 状态:$f[k, i, j]$表示只经过编号不超过k的节点,从i到j的最短路长度
|
||
- 状态转移:`f[k,i,j] = min(f[k-1, i, j], f[k-1, i, k] + f[k-1, k, j])`。决策就是要不要经过k这个点。
|
||
- 显然第一维可以推掉,所以实际上数组是两维度的
|
||
- 实现
|
||
```c
|
||
int dis[maxn][maxn];
|
||
memset(dis, 0x3f, sizeof(dis));
|
||
for (int i = 1; i <= n; ++ i) dis[i][i] = 0;
|
||
for (int i = 1; i <= m; ++ i) {
|
||
int u = read(), v = read(), w = read();
|
||
dis[u][v] = dis[v][u] = w;
|
||
}
|
||
for (int k = 1; k <= n; ++ k){
|
||
for (int i = 1; i <= n; ++ i) {
|
||
for (int j = 1; j <= n; ++ j){
|
||
f[i][j] = min(f[i][j], f[i][k] + f[k][j]);
|
||
}
|
||
}
|
||
}
|
||
``` |