状态机dp入门-股票买卖 I、II、III

股票买卖 I

题目描述

在这里插入图片描述

输入描述

在这里插入图片描述

输出描述

在这里插入图片描述

输入样例1

6
7 1 5 3 6 4

输出样例1

1

输入样例2

5
7 6 4 3 1

输出样例2

0

样例说明

在这里插入图片描述


普普通通的一题,采用贪心解决即可。

对于每个读入的数,不断更新从当前时刻开始买入的最小值,同时更新若在当前时刻卖出能够获得的利润最大值。

当呈现样例 2 的暴跌状态时,不做任何交易为最优解。

参考代码

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

const int N=1e5+10;
int a[N];

int main(){
    
    
	int n;
	cin>>n;
	for(int i=1;i<=n;i++){
    
    
		cin>>a[i];
	}
	int minn=a[1];
	int ans=0;
	for(int i=2;i<=n;i++){
    
    
		minn=min(minn,a[i]);
		ans=max(ans,a[i]-minn);
	}
	cout<<ans;
	return 0;
}

股票买卖 II

题目描述

在这里插入图片描述

输入描述

输出描述

在这里插入图片描述

输入样例1

6
7 1 5 3 6 4

输出样例1

7

输入样例2

5
1 2 3 4 5

输出样例2

4

输入样例3

5
7 6 4 3 1

输出样例3

0

样例说明

在这里插入图片描述


本题比 I 代多出的条件是:可以进行多次交易,但必须在购买前卖出之前持有的股票。

显然本题仍然可用 贪心 + 单调 的方法来做,某一段下降序列的终点即为买入的最优价格,而接下来一段上升序列的终点即为卖出的最优价格,重复直至遍历完整个序列即可。同时注意 flag 标记当前手中是否持有股票。

但毕竟本系列题的终点是状态机 dp ,所以本题当然还是 dp 的好啦。

此时若单纯使用类似 LIS 的一维 dp,会发现一维 dp 所能表示的仅有天数,而无法同时标记手中是否持有股票这一状态,由此可能同时参与至多笔交易中。因此,需要新增一维以表示是否持有股票这一状态,而对于该类新增了若干维度以表示新增的若干状态的,属于状态机 dp 。

由此,对于第 i 天的状态共存在两种可能:f [ i ] [ 0 ] 或 f [ i ] [ 1 ],其中第二维的 0 表示当前手中没有股票,可以进行买入,1 表示当前手中有股票,只能进行卖出。

因此,可转移到以上两种情况的状态分别是:

① 前一天手中没有股票,并决定第二天不购买; f [ i ] [ 0 ] = f [ i - 1 ] [ 0 ]

② 前一天手中持有股票,决定在第二天卖出; f [ i ] [ 0 ] = f [ i - 1 ] [ 1 ] + w [ i ]

③ 前一天手中持有股票,并决定第二天不卖出; f [ i ] [ 1 ] = f [ i - 1 ] [ 1 ]

④ 前一天手中没有股票,决定在第二天买入; f [ i ] [ 1 ] = f [ i - 1 ] [ 0 ] - w [ i ]

其中,w [ i ] 代表第 i 天股票的价格。

而结束的状态为 f [ n ] [ 0 ] ,因为若在最后一天手中持有股票,一定不如在购买的那一日选择【不购买】的状态更优。

参考代码

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

const int N=1e5+10,inf=0x3f3f3f3f;
int a[N];
int f[N][2];

int main(){
    
    
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>a[i];
	f[0][0]=0;
	f[0][1]=-inf;
	for(int i=1;i<=n;i++){
    
    
		f[i][1]=max(f[i-1][1],f[i-1][0]-a[i]);
		f[i][0]=max(f[i-1][0],f[i-1][1]+a[i]);
	}
	cout<<f[n][0];
	return 0;
}

股票买卖 III

题目描述

在这里插入图片描述

输入描述

在这里插入图片描述

输出描述

在这里插入图片描述

输入样例1

8
3 3 5 0 0 3 1 4

输出样例1

6

输入样例2

5
1 2 3 4 5

输出样例2

4

输入样例3

5
7 6 4 3 1

输出样例3

0

样例说明

在这里插入图片描述


本题比 II 中新增的约束是:最多只能完成两笔交易。

显然,之前的二维 dp 已经不再能满足本题的需求,因为本题中还需要记录已经完成了多少次交易,由此不得不新增一维以 套娃的 标识新状态。 即 f [ i ] [ k ] [ 0/1 ],其中 i 表示天数,k 表示交易次数,0/1 即标识手中是否有货,显然 k 的限制条件是小于等于 2 。

对于本题中状态的转移仅需要多考虑第二维,即每次买入后令交易次数加一(因为每次买入后都会有对应的卖出操作,若一直将股票留到结束显然不是最优解)。注意枚举答案时需要枚举交易次数,因为在样例 3 中显然交易次数为 0 是比其他选择更优的。

参考代码

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

const int N=1e5+10,inf=0x3f3f3f3f;
int a[N];
int f[N][3][2];

int main(){
    
    
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>a[i];
	
    for(int i = 0; i <= n; i++)
        for(int j = 0; j <= 2; j++){
    
    
            f[i][j][0] = -inf;
            f[i][j][1] = -inf;
        }
    for(int i = 0; i <= n; i++)
		f[i][0][0] = 0;
		
	for(int i=1;i<=n;i++){
    
    
		for(int k=1;k<=2;k++){
    
    
			f[i][k][0]=max(f[i-1][k][0],f[i-1][k][1]+a[i]);
			f[i][k][1]=max(f[i-1][k][1],f[i-1][k-1][0]-a[i]);
		}
	}
	int ans=0;
	for(int k=0;k<=2;k++){
    
    
		ans=max(ans,f[n][k][0]);
	}
	cout<<ans<<endl;
	return 0;
}

猜你喜欢

转载自blog.csdn.net/laysan/article/details/121151647
今日推荐