AcWing算法提高课-1.4.2股票买卖 IV

算法提高课整理

CSDN个人主页:更好的阅读体验

Start

原题链接
题目描述

给定一个长度为 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 1N105,
1 ≤ k ≤ 100 1 \le k \le 100 1k100


思路

本题为 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 00 继续不持有股票;
      • 0 → 1 0\rightarrow 1 01 买当天的股票;
      • 1 → 0 1\rightarrow 0 10 卖出手里的股票;
      • 1 → 1 1\rightarrow 1 11 继续持有股票。
    • 解决了状态转移的问题,考虑设计状态转移方程。
    • 观察下图状态机,我们发现:
      • 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} fi1,j,0,fi1,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} fi1,j,1,fi1,j1,0 转移过来,因此状态转移方程为:f[j][1] = max(f[j][1], f[j - 1][0] - a[i]);

ztj

  • 初始化

    • 由于有的状态值为负数,对应到实际情况就是亏钱的股票买卖,所以我们即使求最大值也应该将所有状态都初始化为 − ∞ -\infty
    • f[0][0][0] = 0; 什么都没有,当然是 0 0 0 咯~
  • 目标状态: f n , 0 ∼ k , 0 f_{n,0\sim k,0} fn,0k,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;
}

228aa7bed3e021faf24cf8560d3e47bb.gif

最后,如果觉得对您有帮助的话,点个赞再走吧!

  • 21
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星河依旧长明

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值