题目描述
给定一个正整数数组𝐴[1,2, … , 𝑛]
,现要从中选出一些数,满足数组中任意相邻的 3 个数最多有一个可被选中(即对任意𝑖,𝐴[𝑖 − 1]
, 𝐴[𝑖]
, 𝐴[𝑖 + 1]
三个数最多可被选中一个)。 请设计一算法使得选出的数总和最大。
问题分析
由于数组中任意相邻的 3 个数中最多有 1 个可被选中。因此,每个被选中的数之间至少要间隔两个数。
每一个数都有选和不选两种状态。若当前数不选,可以从上一个数状态的最大值转移过来;若选择当前数,则只能从上一个空两个数的状态最大值转移过来。
算法描述
状态定义:dp[i][j]
表示第 i 个数处于状态 j 的最大和。j = 0
表示第 i 个数不选,j = 1
表示第 i 个数要选。
状态转移:
- 第 i 个数不选:
dp[i][0] = max(dp[i-1][0], dp[i-1][1])
- 第 i 个数要选:
dp[i][1] = max(dp[i-3][1], dp[i-3][0]) + a[i]
边界条件:
dp[1][1] = a[1]
dp[2][1] = a[2]
状态压缩
通过观察状态转移方程可以发现,状态转移过程中,只用到dp[i][0]
和dp[i][1]
二者的最大值,因此,状态转移方程可以改写成如下形式:
dp[i] = max(dp[i-3] + a[i], dp[i-1])
此时,状态定义dp[i]
可解释为前 i 个数的所选数的最大和。
复杂度分析
只需要遍历一遍数组,因此时间复杂度是 O ( n ) O(n) O(n)
程序代码
未状态压缩:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
int n;
cin >> n;
vector<int> a(n + 1);
for(int i = 1; i <= n; i++) {
cin >> a[i];
}
if(n == 1) {
cout << a[1] << endl;
return 0;
}
else if(n == 2) {
cout << max(a[1], a[2]) << endl;
return 0;
}
vector<vector<int>> dp(n + 1, vector<int>(2, 0));
dp[1][1] = a[1];
dp[2][1] = a[2];
for(int i = 3; i <= n; i++) {
dp[i][1] = max(dp[i-3][1], dp[i-3][0]) + a[i];
dp[i][0] = max(dp[i-1][1], dp[i-1][0]);
}
cout << max(dp[n][1], dp[n][0]) << endl;
return 0;
}
状态压缩
#include <iostream>
#include <vector>
using namespace std;
int main()
{
int n;
cin >> n;
vector<int> a(n + 1);
for(int i = 1; i <= n; i++) {
cin >> a[i];
}
if(n == 1) {
cout << a[1] << endl;
return 0;
}
else if(n == 2) {
cout << max(a[1], a[2]) << endl;
return 0;
}
vector<int> dp(n + 1, 0);
dp[1] = a[1];
dp[2] = a[2];
for(int i = 3; i <= n; i++) {
dp[i] = max(dp[i-3] + a[i], dp[i-1]);
}
cout << dp[n] << endl;
return 0;
}