题目描述
给定一个长度为 N N N 的数组,数组中的第 i i i 个数字表示一个给定股票在第 i i i 天的价格。
设计一个算法来计算你所能获取的最大利润,你最多可以完成 k k k 笔交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。一次买入卖出合为一笔交易。
输入格式
第一行包含整数
N
N
N 和
k
k
k,表示数组的长度以及你可以完成的最大交易笔数。
第二行包含 N N N 个不超过 10000 的正整数,表示完整的数组。
输出格式
输出一个整数,表示最大利润。
数据范围
1
≤
N
≤
1
0
5
,
1≤N≤10^5,
1≤N≤105,
1
≤
k
≤
100
1≤k≤100
1≤k≤100
输入样例1:
3 2
2 4 1
输出样例1:
2
输入样例2:
6 2
3 2 6 5 0 3
输出样例2:
7
样例解释
样例1:在第 1 天 (股票价格 = 2) 的时候买入,在第 2 天 (股票价格 = 4) 的时候卖出,这笔交易所能获得利润 = 4-2 = 2 。
样例2:在第 2 天 (股票价格 = 2) 的时候买入,在第 3 天 (股票价格 = 6) 的时候卖出, 这笔交易所能获得利润 = 6-2 = 4 。随后,在第 5 天 (股票价格 = 0) 的时候买入,在第 6 天 (股票价格 = 3) 的时候卖出, 这笔交易所能获得利润 = 3-0 = 3 。共计利润 4+3 = 7.
首先,应列出该问题下的状态机模型:
那么在任意一天中,应该只存在 “手中无股” 和 “手中有股” 两种状态。
在f[i][j][1/0]
如图中含义的基础下,各种状态转移的具体含义是:
注*: 这里的状态转移均是考虑第 i − 1 i - 1 i−1到第 i i i天或者第 j − 1 j - 1 j−1笔交易到第 j j j笔交易时的状态。
- 手中有股(1) —> 手中无股(0):说明此时进行了股票抛售,得到的利润应是
+ price[i]
,由于该事件发生后才能算作一笔完整的交易,所以它们在一个共同的j下完成: f ( i , j , 0 ) = f ( i − 1 , j , 1 ) + p r i c e [ i ] f(i,j,0)=f(i-1,j,1)+price[i] f(i,j,0)=f(i−1,j,1)+price[i]。- 手中有股(1) —> 手中有股(1):说明该股票没动,也就不会获得利润
+ 0
,也就是交易数还处在第 j j j笔,对应地: f ( i , j , 1 ) = f ( i − 1 , j , 1 ) f(i,j,1)=f(i-1,j,1) f(i,j,1)=f(i−1,j,1)。- 手中无股(0) —> 手中有股(1):说明该股票买入了,买入就需要花钱,所以利润为负值
- price[i]
,那么买入该股票后交易笔数也就到了新的一轮,如果当前交易的是第 j j j笔交易,那就应该由 j − 1 j-1 j−1笔交易转移而来: f ( i , j , 1 ) = f ( i − 1 , j − 1 , 0 ) − p r i c e [ i ] f(i,j,1)=f(i-1,j-1,0)-price[i] f(i,j,1)=f(i−1,j−1,0)−price[i]。- 手中无股(0) —> 手中无股(0):说明该股票没动,也就不会获得利润
+ 0
,也就是交易数还处在第 j j j笔,对应地: f ( i , j , 0 ) = f ( i − 1 , j , 0 ) f(i,j,0)=f(i-1,j,0) f(i,j,0)=f(i−1,j,0)。决策:
max
。
在初始状态下,
f
(
i
,
0
,
1
)
f(i,0,1)
f(i,0,1)这类状态是不合法的,为什么呢?因为
f
(
i
,
0
,
1
)
f(i,0,1)
f(i,0,1)的释义是:第
i
i
i 天持有第0笔交易的股票,而股票完成笔数必须是≥1的,也即要想持有股票,必须至少处在第1笔交易;所以为了不让这类非法状态转移到其它状态,就需要同对它赋一些不合法的初值,可以是
−
∞
-∞
−∞,这样max
必定不会选中它。
但是
f
(
i
,
0
,
0
)
f(i,0,0)
f(i,0,0)这些状态是合法的,就为0,表示没开始进行股票交易时不持股,其实它也就是上图中状态机的“入口”。
最后,由于 k k k只是表示的最多交易的笔数,不一定就是完成到了 k k k笔交易,所以需要枚举一下各笔交易下得到的利润最大值。
C++代码
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1e5 + 10, M = 110;
int n, k;
int p[N], f[N][M][2];
int main(){
memset(f, -0x3f, sizeof f);
ios :: sync_with_stdio(false);
cin >> n >> k;
for(int i = 1;i <= n;i ++) cin >> p[i];
for(int i = 0;i <= n;i ++) f[i][0][0] = 0;
for(int i = 1;i <= n;i ++)
for(int j = 1;j <= k;j ++){
//每个状态分别max
f[i][j][0] = max(f[i - 1][j][0], f[i - 1][j][1] + p[i]);
f[i][j][1] = max(f[i - 1][j][1], f[i - 1][j - 1][0] - p[i]);
}
int ans = 0;
for(int i = 1;i <= k;i ++) ans = max(ans, f[n][i][0]);
cout << ans << endl;
return 0;
}
观察一下其实可以对代码做等价变形,由于状态转移式子的右边均是i - 1
维的,这就联想到了0-1背包里的滚动数组优化,把i
的这一层的存储删去,f
就优化到了二维。
二维Code
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1e5 + 10, M = 110;
int n, k;
int p[N], f[M][2];
int main(){
memset(f, -0x3f, sizeof f);
ios :: sync_with_stdio(false);
cin >> n >> k;
for(int i = 1;i <= n;i ++) cin >> p[i];
f[0][0] = 0;
for(int i = 1;i <= n;i ++)
for(int j = k;j >= 1;j --){
//每个状态分别max
f[j][0] = max(f[j][0], f[j][1] + p[i]);
f[j][1] = max(f[j][1], f[j - 1][0] - p[i]);
}
int ans = 0;
for(int i = 1;i <= k;i ++) ans = max(ans, f[i][0]);
cout << ans << endl;
return 0;
}