文章目录
0. 前言
相关:
1. 数字三角形+模板题
理解:[线性dp] 摘花生(数字三角形模型)向下、向右每次只走一次,到达右下角即终止,即一条路线。本题也是只能向下、向右走,但是需要走两次,即两条路线,当两条路线走到了相同格子,则当前格子只进行一次的累加(这个对应到题目来讲就是,一条路径走过使得当前格子变为 0),针对不同格子就进行两次的累加。
重点: 线性 dp
、数字三角形模型
思路:
- 状态定义:
- 摘花生走一次用
f[i][j]
表示其中一条路线,针对走两次可做简单拓展 f[i1][j1][i2][j2]
表示从起点[1,1]
、[1,1]
走到[i1,j1]
、[i2,j2]
点的所有路径和的最大值- 当
i1+j1=i2+j2=k
时,这两条路径才可能会走到同一个格子中。k
表示两条路线当前走到的格子横纵坐标之和 - 当
i1+j1=i2+j2
相等时,两条路线不一定走到同一格子中,但是在同一格子中的话,即有i1=i2
,一定满足i1+j1=i2+j2
。所以,我们可以枚举k、i1、i2
,此时j1=k-i1
、j2=k-i2
,当i1=i2
时,针对同一个k
则必定有j1=j2
就能够判断出是处于相同格子下,这样就能简化一维的枚举 - 故,状态可定义为
f[k][i1][i2]
表示所有从起点[1,1]
、[1,1]
走到[i1,k-i1]
、[i2,k-i2]
点的所有路径和的最大值
- 摘花生走一次用
- 状态转移:
- 针对两条路径,其分别只能走向
下、右
这两个方向,那么针对f[k][i1][i2]
状态的转移就有以下 4 种情况: - 1下2下:最后一步是向下走才到达
f[k][i1][i2]
点的。那么显然前一个状态的最大值就是f[k-1][i1-1][i2-1]
,当两条路径没有走到同一个格子的时候,我们需要加上w[i1][j1]+w[i2][j2]
,当两条路径走到同一个格子的时候,我们只能加一个格子,相当于另一个格子加 0,在此即为w[i1][j1]
- 1下2右、1右2下、1右2右 分析方式同理
- 状态转移方程: 首先枚举
k
,从2~n+n
枚举,然后枚举i1
、i2
。当在一个k
下有i1 = i2
则说明是重合格子。则只用加一遍当前格子,否则需要将不同的两个格子都进行状态累加,转移int t = w[i1][j1]; if (i1 != i2) t += w[i2][j2]; // 如果不是同一个格子,则累加 f[k][i1][i2] = max(f[k][i1][i2], f[k - 1][i1 - 1][i2 - 1] + t); // 四种情况的状态转移,下下 f[k][i1][i2] = max(f[k][i1][i2], f[k - 1][i1 - 1][i2] + t); // 下右 f[k][i1][i2] = max(f[k][i1][i2], f[k - 1][i1][i2 - 1] + t); // 右下 f[k][i1][i2] = max(f[k][i1][i2], f[k - 1][i1][i2] + t); // 右右
- 针对两条路径,其分别只能走向
- 边界情况及初始化:
- 全局数组自动初始化为 0,取
max
操作,不需要初始化其它特殊位置 - 枚举一定要想清楚,先枚举横纵坐标之和
k
,用k
,再配合上两个路径的横坐标i1、i2
就能判断是否为重合路径。这点很重要。i1+j1=i2+j2=k
不代表它一定是重合格子,但是重合格子一定满足该式。当i1=i2
,则对同一个k
,也必定满足j1=j2
则为同一个格子。这点转化很是巧妙 - 答案即为:
f[n+n][n][n]
- 全局数组自动初始化为 0,取
2000 年 NOIP
提高组的 DP
题目,很有意思。是摘花生的加强版,针对相同格子判断这个操作,挺秀的,实力用 k
简化掉了一维的枚举。
当然,这玩意还能拓展到 k
种路径。这已经无法拿 dp
来解来了。貌似是最小费用流问题,属于图论问题了。
写法一代码:
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 15;
int n;
int w[N][N];
int f[N + N][N][N];
int main() {
cin >> n;
int a, b, c;
while (cin >> a >> b >> c, a || b || c) w[a][b] = c;
for (int k = 2; k <= n + n; ++k)
for (int i1 = 1; i1 <= n; ++i1)
for (int i2 = 1; i2 <= n; ++i2) {
int j1 = k - i1, j2 = k - i2;
if (j1 >= 1 && j1 <= n && j2 >= 1 && j2 <= n) {
int t = w[i1][j1];
if (i1 != i2) t += w[i2][j2];
f[k][i1][i2] = max(f[k][i1][i2], f[k - 1][i1 - 1][i2 - 1] + t);
f[k][i1][i2] = max(f[k][i1][i2], f[k - 1][i1 - 1][i2] + t);
f[k][i1][i2] = max(f[k][i1][i2], f[k - 1][i1][i2 - 1] + t);
f[k][i1][i2] = max(f[k][i1][i2], f[k - 1][i1][i2] + t);
}
}
cout << f[n + n][n][n] << endl;
return 0;
}