题目描述
难度分:1400
输入n(1≤n≤105)和一个2行n列的矩阵a(1≤a[i][j]≤109)。
你需要从矩阵中选择一些数,要求任意两数不能左右相邻,也不能上下相邻。
输出所选数字之和的最大值。
输入样例1
5
9 3 5 7 3
5 8 1 4 5
输出样例1
29
输入样例2
3
1 2 9
10 1 1
输出样例2
19
输入样例3
1
7
4
输出样例3
9
算法
动态规划
状态定义
利用二进制状态压缩的思想,用0表示某一列没有选数,用1表示选了第1行的数,用2表示选了第2行的数。而所选的数不能上下相邻,所以不存在一列中两个数都选的情况,只有这三种状态。用f[i][0/1/2]表示第i列状态为0/1/2时,列[1,i]上所选数的最大和,显然在这种状态下,答案就是max2mask=0f[n][mask]。
状态转移
- 当第i列状态为0时,前一列是什么状态都无所谓,状态转移方程为f[i][0]=max(f[i−1][0],f[i−1][1],f[i−1][2]);
- 当第i列状态为1时,前一列只能是0或2的状态,状态转移方程为f[i][1]=max(f[i−1][0],f[i−1][2]);
- 当第i列状态为2时,前一列只能是0或1的状态,状态转移方程为f[i][2]=max(f[i−1][0],f[i−1][1])。
复杂度分析
时间复杂度
状态数量为3n,单次转移的时间复杂度为O(1),因此整体就是一个普通的线性DP
,时间复杂度为O(n)。
空间复杂度
空间的消耗就是DP
数组fn×3,额外空间复杂度为O(n)(由于每一列的状态只依赖于前一列的状态,因此可以通过滚动数组优化成O(1))。
C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 100010;
int a[2][N], n;
LL f[N][4];
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d", &a[0][i]);
}
for(int i = 1; i <= n; i++) {
scanf("%d", &a[1][i]);
}
for(int i = 1; i <= n; i++) {
f[i][0] = max(f[i - 1][0], max(f[i - 1][1], f[i - 1][2])); // 第i列不选数
f[i][1] = max(f[i - 1][0], f[i - 1][2]) + a[0][i]; // 第i列选第一行的数
f[i][2] = max(f[i - 1][0], f[i - 1][1]) + a[1][i]; // 第i列选第二行的数
}
printf("%lld\n", max(f[n][0], max(f[n][1], f[n][2])));
return 0;
}