算法提高课整理
CSDN个人主页:更好的阅读体验
原题链接
题目描述
给定一个长度为 n n n 的数组,数组中的第 i i i 个数字表示一个给定股票在第 i i i 天的价格。
设计一个算法来计算你所能获取的最大利润,你最多可以完成 k k k 笔交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。一次买入卖出合为一笔交易。
输入格式
第一行包含整数 n , k n,k n,k,表示数组的长度以及你可以完成的最大交易笔数。
第二行包含 n n n 个不超过 1 0 4 10^4 104 的正整数,表示完整的数组。
输出格式
输出一个整数,表示最大利润。
数据范围
1
≤
N
≤
1
0
5
1 \le N \le 10^5
1≤N≤105,
1
≤
k
≤
100
1 \le k \le 100
1≤k≤100
思路
本题为 DP 问题,可以使用 闫氏DP分析法 解题。
DP:
- 状态表示
f
i
,
j
,
0
/
1
f_{i,j,0/1}
fi,j,0/1:
- 集合:在前 i i i 天中进行买卖,第 i i i 天【持有 ( 1 ) (1) (1) | 不持有 ( 0 ) (0) (0)】股票且已经完成 j j j 笔完整的交易(先卖出后买入)的所有方案的集合。
- 属性: max \max max
- 状态计算:
- 本题状态较复杂,如何用
0
/
1
0/1
0/1 表示各种状态转移?
- 0 → 0 0\rightarrow 0 0→0 继续不持有股票;
- 0 → 1 0\rightarrow 1 0→1 买当天的股票;
- 1 → 0 1\rightarrow 0 1→0 卖出手里的股票;
- 1 → 1 1\rightarrow 1 1→1 继续持有股票。
- 解决了状态转移的问题,考虑设计状态转移方程。
- 观察下图状态机,我们发现:
-
f
i
,
j
,
0
f_{i,j,0}
fi,j,0 由上一层的两个状态
f
i
−
1
,
j
,
0
,
f
i
−
1
,
j
,
1
f_{i-1,j,0},f_{i-1,j,1}
fi−1,j,0,fi−1,j,1 转移过来,因此状态转移方程为:
f[j][0] = max(f[j][0], f[j][1] + w[i]);
-
f
i
,
j
,
1
f_{i,j,1}
fi,j,1 由上一层的两个状态
f
i
−
1
,
j
,
1
,
f
i
−
1
,
j
−
1
,
0
f_{i-1,j,1},f_{i-1,j-1,0}
fi−1,j,1,fi−1,j−1,0 转移过来,因此状态转移方程为:
f[j][1] = max(f[j][1], f[j - 1][0] - a[i]);
-
f
i
,
j
,
0
f_{i,j,0}
fi,j,0 由上一层的两个状态
f
i
−
1
,
j
,
0
,
f
i
−
1
,
j
,
1
f_{i-1,j,0},f_{i-1,j,1}
fi−1,j,0,fi−1,j,1 转移过来,因此状态转移方程为:
- 本题状态较复杂,如何用
0
/
1
0/1
0/1 表示各种状态转移?
-
初始化
- 由于有的状态值为负数,对应到实际情况就是亏钱的股票买卖,所以我们即使求最大值也应该将所有状态都初始化为 − ∞ -\infty −∞。
f[0][0][0] = 0;
什么都没有,当然是 0 0 0 咯~
-
目标状态: f n , 0 ∼ k , 0 f_{n,0\sim k,0} fn,0∼k,0(即所有日期都考虑了,买卖次数不超过 k k k 次,最后手里不剩股票的所有状态)。
疑难解答
Q:为什么状态的设计是先卖出再买入呢?题中不是先买入嘛?
A:第一支股票第一次操作只有买或不买,一定不可能是卖或不卖,因此第一支股票买入时必须按照一次交易处理。
算法
时间复杂度 O ( n k ) O(nk) O(nk),空间复杂度 O ( n k ) O(nk) O(nk)。
发现空间卡的很紧,容易 MLE。
注意到每次转移全部用的上一层的状态,因此我们考虑滚动数组优化,直接删掉 f f f 数组的第一维,还是正确的。
AC Code
C + + \text{C}++ C++
#include <iostream>
#include <cstring>
using namespace std;
const int N = 100010, M = 110;
int n, m;
int a[N];
int f[M][2]; // 滚动数组
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ )
scanf("%d", &a[i]);
memset(f, -0x3f, sizeof f);
f[0][0] = 0; // 初始化
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
{
f[j][0] = max(f[j][0], f[j][1] + a[i]);
f[j][1] = max(f[j][1], f[j - 1][0] - a[i]);
}
int res = 0;
for (int i = 0; i <= m; i ++ )
res = max(res, f[i][0]);
printf("%d\n", res);
return 0;
}
最后,如果觉得对您有帮助的话,点个赞再走吧!