AcWing 股票买卖 系列(其他做法 & DP)

1054. 股票买卖

题目传送门:1054. 股票买卖
题意:
给定n天的股票价格,最多只允许完成一笔交易(即买入和卖出一支股票),求最大利润。

输入格式
第一行包含整数 N,表示数组长度。
第二行包含 N 个不大于 109 的正整数,表示完整的数组。
输出格式
输出一个整数,表示最大利润。
输入样例1:
6
7 1 5 3 6 4
输出样例1:
5

题解: 最基本版本,对于问题的答案可以这样理解:我在第 i 天卖出,则
利润 = 第 i 天价格 - 前 i 天最小价格。 而前 i 天最小价格是可以随着读入数据更新的,所以只要边读边更新,就做完了(另一种做法是单调栈,稍微麻烦一点)。

#include<bits/stdc++.h>
using namespace std;

int main(){
	int n, t, Max = 0;
	cin >> n;
	int nowmin = 1 << 30;
	for(int i = 0; i < n; ++i){
		cin >> t;
		nowmin = min(nowmin, t);
		Max = max(Max, t - nowmin);	//	第i天卖出的最大利润
	}
	cout << Max << endl;
	return 0;
}

1055. 股票买卖 II

题目传送门:1055. 股票买卖 II
题意:
给定n天的股票价格,可以尽可能地完成更多的交易,求最大利润。
注意:你不能同时参与多笔交易(手中没有持股才能买新股)。
输入格式
第一行包含整数 N,表示数组长度。
第二行包含 N 个不大于 10000 的正整数,表示完整的数组。
输出格式
输出一个整数,表示最大利润。
数据范围
1≤N≤105
输入样例1:
6
7 1 5 3 6 4
输出样例1
7
输入样例2:
5
1 2 3 4 5
输出样例2:
4

题解: 可dp,这里贴上单调栈做法。由于可以无限次买卖,那每个递增区间的两端一定都是买和卖的点,而每个这样的递增区间都可以用单调栈求出。
单调栈AC代码:

#include<bits/stdc++.h>
using namespace std;
int sta[100000010] = {0}, top = 0, res = 0;

int main(){
	int n, t;
	cin >> n;
	cin >> t;
	sta[++top] = t;
	for(int i = 1; i < n; ++i){
		cin >> t;
		if(t >= sta[top]){
			res += t - sta[top];
			sta[++top] = t;
		}
		else{
			while(sta[top] > t)
				top--;
			sta[++top] = t;
		}
	}
	cout << res << endl;
	return 0;
}

1056. 股票买卖 III

题目传送门:1056. 股票买卖 III
题意:
给定n天的股票价格,最多买卖2次,求最大利润。
注意:你不能同时参与多笔交易(手中没有持股才能买新股)。
输入格式
第一行包含整数 N,表示数组长度。
第二行包含 N 个不大于 109 的正整数,表示完整的数组。
输出格式
输出一个整数,表示最大利润。
数据范围
1≤N≤105
输入样例:
8
3 3 5 0 0 3 1 4
输出样例:
6

题解: 前后缀的做法。
思路:由于是最多买卖2次,因此存在一个中间段,在此之前进行了一次买卖,之后进行了第二次买卖。可以借助上面的最基础版本的前缀区间的方法,我们将问题变成两个区间的问题:
answer = 前 i 天 交易 的最大利润 + 第 i 天 买进的最大利润
(注意定义,前 i 天说明并不关心是在哪天卖出,第 i 天买进说明是这一天买进,但不关心哪天卖出)
两个问题的解用 first[i] 和 second[i] 表示,前者顺序扫描一遍求出,后者倒序扫描一遍求出。最后通过枚举中间点,找到最大利润。

#include<bits/stdc++.h>
using namespace std;

int first[100010], second[100010], a[100010];

int main(){
	int n;
	cin >> n;
	for(int i = 0; i < n; ++i)
		cin >> a[i];
	int nowmin = a[0], nowmax = a[n-1];
	for(int i = 1; i < n; ++i){
		nowmin = min(nowmin, a[i]);
		first[i] = max(first[i-1], a[i] - nowmin);	//	前i天卖出的最大利润
	}
	for(int i = n-1; i >= 0; --i){
		nowmax = max(nowmax, a[i]);
		second[i] = nowmax - a[i];	//	第i天买进的最大利润
	}
	int Max = 0;
	for(int i = 0; i < n; ++i)
		Max = max(Max, first[i] + second[i]);
	cout << Max << endl;
	
	return 0;
}

1057. 股票买卖 IV

题目传送门:1057. 股票买卖 IV
给定n天的股票价格,最多买卖k次,求最大利润。
注意:你不能同时参与多笔交易(手中没有持股才能买新股)。
输入格式
第一行包含整数 N 和 k,表示数组的长度以及你可以完成的最大交易数量。
第二行包含 N 个不超过 10000 的正整数,表示完整的数组。
输出格式
输出一个整数,表示最大利润。
数据范围
1≤N≤105,
1≤k≤100
输入样例1:
3 2
2 4 1
输出样例1:
2
输入样例2:
6 2
3 2 6 5 0 3
输出样例2:
7

题解: DP做法,前两题本也可用。
dp[0/1][i][j] 表示目前手上 没有/有股票,且前 i 天里买了 j 次股票 的最大利润。
设now为当天的股价
dp[0][i][j] = max( dp[0][i-1][j], dp[1][i-1][j] + now )
dp[1][i][j] = max( dp[1][i-1][j], dp[0][i-1][j-1] - now )

即前 i 天买了 j 次,当前手上没股,可能是第 i 天没买,或者第 i 天刚卖的情况(卖出所以加上当天的利润now)。
前 i 天买了 j 次,当前手上有股,可能是前一天就有的股今天没卖,或者今天刚买入。
由于只和最新两天的状态有用,所以滚动数组,j 要倒序遍历。
初始化时除了dp[0][0]的状态为0,其他都置为负无穷,因为有时买股票会使当前赚的钱变成负数。
最后的答案肯定是股票都卖出去的情况,所以每次通过dp[0][i][j]更新即可。

#include<bits/stdc++.h>
using namespace std;

int dp[2][110];

int main(){
	int n, k, now, Max = 0;
	cin >> n >> k;
	memset(dp, -0x3f3f3f, sizeof dp);
	dp[0][0] = 0;
	for(int i = 0; i < n; ++i){
		cin >> now;
		for(int j = k; j > 0; --j){
			dp[0][j] = max(dp[0][j], dp[1][j] + now);
			dp[1][j] = max(dp[1][j], dp[0][j-1] - now);
			Max = max(Max, dp[0][j]);
		}
	}
	cout << Max << endl;

	return 0;
}

1058. 股票买卖 V

题目传送门:1058. 股票买卖 V
题意:
给定n天的股票价格,可以买卖无限次,但每次卖出以后,必须等待一天的冷冻期才可以再买下一个,求最大利润。
注意:你不能同时参与多笔交易(手中没有持股且不在冷冻期才能买新股)。
输入格式
第一行包含整数 N,表示数组长度。
第二行包含 N 个不超过 10000 的正整数,表示完整的数组。
输出格式
输出一个整数,表示最大利润。
数据范围
1≤N≤105
输入样例:
5
1 2 3 0 2
输出样例:
3
样例解释
对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出],第一笔交易可得利润 2-1 = 1,第二笔交易可得利润 2-0 = 2,共得利润 1+2 = 3。

思路:
开设3个状态:
0代表刚卖出,进入了冷冻期
1代表买进过,手中有股票
2代表冷冻期结束,可以买进的状态

dp[0/1/2][i] 表示该第 i 天为该状态下的最大收益,now是当天股价。
由0的定义可知 dp[0][i] 的前一状态一定是 dp[1][i-1]
==> dp[0][i] = dp[1][i-1] + now;
dp[1][i] 可能是今天无操作,或前一天过了冷冻期,今天买入
==> dp[1][i] = max(dp[1][i-1], dp[2][i-1] - now);
dp[2][i] 可能是之前就脱离了冷冻期,今天无操作的状态,或之前刚卖出的情况。
==> dp[2][i] = max(dp[2][i-1], dp[0][i-1]);
初始状态是可以买进的,属于状态2,所以 dp[2][0] = 0,其他置 -inf。
最终答案一定是0或2状态,选大的一个。
参考:https://www.acwing.com/solution/acwing/content/6175/
代码:

#include<bits/stdc++.h>
using namespace std;

int dp[3][100005];	//	0:刚卖出(进入冷冻期)  1:买进过  2:过了冷冻期之后

int main(){
	int n, now, Max = 0;
	cin >> n;
	memset(dp, -0x3f3f3f, sizeof dp);
	dp[2][0] = dp[0][0] = 0;
	for(int i = 1; i <= n; ++i){
		cin >> now;
		dp[0][i] = dp[1][i-1] + now;
		dp[1][i] = max(dp[1][i-1], dp[2][i-1] - now);
		dp[2][i] = max(dp[2][i-1], dp[0][i-1]);
	}
	Max = max(dp[0][n], dp[2][n]);
	cout << Max << endl;

	return 0;
}

这里可以发现每次dp都只跟上一行的有关,因此只需要开 dp[3][2] 即可,表示天数的维度用滚动数组覆盖,但是反正这样也可以过我也懒着写就不放上来了

1059. 股票买卖 VI

题目传送门:1059. 股票买卖 VI
题意:
给定n天的股票价格,可以尽可能地完成更多的交易,但每次交易要支付k的手续费用,求最大利润。
注意:你不能同时参与多笔交易(手中没有持股才能买新股)。
输入格式
第一行包含两个整数 N 和 f,分别表示数组的长度以及每笔交易的手续费用。
第二行包含 N 个不超过 10000 的正整数,表示完整的数组。
输出格式
输出一个整数,表示最大利润。
数据范围
1≤N≤105,
1≤f≤10000
输入样例:
6 2
1 3 2 8 4 9
输出样例:
8
样例解释
在第 1 天(股票价格 = 1)的时候买入,在第 4 天(股票价格 = 8)的时候卖出,这笔交易所能获得利润 = 8-1-2 = 5 。随后,在第 5 天(股票价格 = 4)的时候买入,在第 6 天 (股票价格 = 9)的时候卖出,这笔交易所能获得利润 = 9-4-2 = 3 。共得利润 5+3 = 8。

题解:
其实这题与上面的1055. 股票买卖 II非常类似,只多了一个手续非的条件。如果第二题是用dp方法做的话,这题只需改动一个条件即可。
思路与上面的同理,就是状态划分,dp[0][i] 表示第 i 天手里没有股票的最大利润,dp[1][i] 代表第 i 天手里有股票的最大利润,但在交易完成时要减去手续费。

#include<bits/stdc++.h>
using namespace std;

int dp[2][100005];	

int main(){
	int n, now, Max = 0, k;
	cin >> n >> k;
	memset(dp, -0x3f3f3f, sizeof dp);
	dp[0][0] = 0;
	for(int i = 1; i <= n; ++i){
		cin >> now;
		dp[0][i] = max(dp[0][i-1], dp[1][i-1] + now - k);//减去手续费
		dp[1][i] = max(dp[1][i-1], dp[0][i-1] - now);
		Max = max(Max, dp[0][i]);
	}
	cout << Max << endl;

	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值