股票买卖类线性Dp

# 标题 通过率 难度
83 股票的最大利润 55.66% 简单
332 股票交易 55.29% 简单
1054 股票买卖 63.55% 简单
1055 股票买卖 II 75.60% 简单
1056 股票买卖 III 57.16% 简单
1057 股票买卖 IV 51.54% 中等
1058 股票买卖 V 66.37% 中等
1059 股票买卖 VI 74.71% 中等
314 低买 57.26% 中等
  1. 纪念品 : https://www.acwing.com/problem/content/description/1165/

股票的最大利润

注意本题不买也可以,而且有可能nums.size()<2;

class Solution {
public:
    int maxDiff(vector<int>& nums) {
        if(nums.size()<2) return 0;
        int cmin=nums[0],ans=0;
        for(int i=1;i<(int)nums.size();i++) {
            ans=max(ans,nums[i]-cmin);
            cmin=min(cmin,nums[i]);
        }
        return ans;
    }
};

股票交易

推一波式子,然后单调队列优化即可。

#include<cstdio>
#include<cstring>
#include<iostream>

using namespace std;
typedef long long LL;
typedef unsigned long long ULL;

const int N=2000+5;

int n,m,w;
int ap[N],bp[N],as[N],bs[N];
int f[N][N];
// f[i][j] 表示第i天有操作并且操作完后剩下j支股票的最大收益。
int g[N],q[N]; 
void relax(int &a,const int &b) { a=((a>b) ? a : b); }

int main()
{
//	freopen("1.in","r",stdin);
	int i,j,k;
	scanf("%d%d%d",&n,&m,&w);
	for(i=1;i<=n;i++) 
		scanf("%d%d%d%d",&ap[i],&bp[i],&as[i],&bs[i]);
	memset(f,-0x3f,sizeof f);
	memset(g,-0x3f,sizeof g);
	g[0]=0;
	for(i=0;i<=n;i++) f[i][0]=0;
	
	int hh,tt;
	for(i=1;i<=n;i++) {
		// 把 i-w-1 天的决策加入集合 
		k=i-w-1;
		if(k>0) {
			for(j=0;j<=m;j++) 
				g[j]=max(g[j],f[k][j]);
		}
		// buy
		hh=tt=0,q[hh]=0;
		for(j=1;j<=m;j++) {
			while(hh<=tt&&q[hh]<j-as[i]) hh++;
			relax(f[i][j],g[q[hh]]+q[hh]*ap[i]-j*ap[i]);
			while(hh<=tt&&g[q[tt]]+q[tt]*ap[i]<=g[j]+j*ap[i]) tt--;
			q[++tt]=j;
		}
		// sell
		hh=tt=0,q[hh]=m;
		for(j=m-1;j>=0;j--) {
			while(hh<=tt&&q[hh]>j+bs[i]) hh++;
			relax(f[i][j],g[q[hh]]+q[hh]*bp[i]-j*bp[i]);
			while(hh<=tt&&g[q[tt]]+q[tt]*bp[i]<=g[j]+j*bp[i]) tt--;
			q[++tt]=j;
		}
			
//		for(j=0;j<=m;j++) {
//			for(r=max(j-as[i],0);r<j;r++) relax(f[i][j],g[r]+(r-j)*ap[i]);
//			for(r=j+1;r<=min(m,j+bs[i]);r++) relax(f[i][j],g[r]+(r-j)*bp[i]);
//		}
//		printf("f[%d][%d] = %d\n",i,j,f[i][j]);
	}
	
	int ans=0;
	for(i=0;i<=n;i++) 
		relax(ans,f[i][0]);
	printf("%d\n",ans);
//	printf("%d\n",f[n][0]);
	return 0;
}

股票买卖

#include<cstdio>
#include<iostream>
typedef long long LL;
typedef unsigned long long ULL;
using namespace std;
const int INF=(-1u)>>1;
const int N=1e5+5;
int a[N];
int n;
int main()
{
//	freopen("1.in","r",stdin);
	int i;
	int ans=0,cmin;
	scanf("%d",&n);
	for(i=1;i<=n;i++) 
		scanf("%d",&a[i]);
	cmin=a[1];
	for(i=2;i<=n;i++) {
		ans=max(ans,a[i]-cmin);
		cmin=min(cmin,a[i]);
	}
	cout<<ans;
	return 0;
}

股票买卖 II

#include<cstdio>
#include<iostream>
typedef long long LL;
typedef unsigned long long ULL;
using namespace std;
const int N=1e5+5;
LL a[N],f[N],cmax;
int n;
int main()
{
//	freopen("1.in","r",stdin);
	int i;
	scanf("%d",&n);
	for(i=1;i<=n;i++) 
		scanf("%lld",&a[i]);
	cmax=-a[1];
	for(i=2;i<=n;i++) {
		f[i]=max(f[i-1],a[i]+cmax);
		cmax=max(cmax,f[i-1]-a[i]);
	}
//	for(i=1;i<=n;i++) 
//		cout<<f[i]<<" ";
	cout<<f[n];
	return 0;
}

股票买卖 III

#include<cstdio>
#include<iostream>
typedef long long LL;
typedef unsigned long long ULL;
using namespace std;
const int N=1e5+5;
int f[N],g[N],a[N];
int n;
int main()
{
//	freopen("1.in","r",stdin);
	int i;
	scanf("%d",&n);
	for(i=1;i<=n;i++) 
		scanf("%d",&a[i]);
	int cmin=a[1];
	for(i=2;i<=n;i++) {
		f[i]=max(f[i-1],a[i]-cmin);
		cmin=min(a[i],cmin);
	}
	int cmax=a[n];
	for(i=n-1;i>=1;i--) {
		g[i]=max(g[i+1],cmax-a[i]);
		cmax=max(cmax,a[i]);
	}
	int ans=0;
	for(i=2;i<=n-1;i++) 
		ans=max(ans,f[i]+g[i]);
	cout<<ans;
	return 0;
}

注意到 \(dalao\) 有跟我不一样的解法: https://www.acwing.com/solution/content/5049/

股票买卖 IV

#include<cstdio>
#include<iostream>
typedef long long LL;
typedef unsigned long long ULL;
using namespace std;
const int N=1e5+5,K=105;
int f[N][K],a[N],cmax[K];
int n,m;
// f[i][k] 表示选完了第i天的股票,所得到的最大利润,(第i天可选可不选) ;
// f[i][k]=max(f[i-1][k],f[j][k-1]+a[i]-a[j]), 0<j<i;
int main()
{
//	freopen("1.in","r",stdin);
	int i,k;
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;i++) 
		scanf("%d",&a[i]);
	fill(cmax,cmax+m+1,-a[1]);
	for(i=2;i<=n;i++) {
		for(k=m;k>=1;k--) { 
			f[i][k]=max(f[i-1][k],a[i]+cmax[k-1]);
			cmax[k]=max(cmax[k],f[i][k]-a[i]);
		}
		cmax[0]=max(cmax[0],-a[i]);
	}
	int ans=0;
	for(i=1;i<=n;i++) 
		for(k=0;k<=m;k++) 
			ans=max(ans,f[i][k]);//,cout<<i<<" "<<k<<":"<<f[i][k]<<endl;
	cout<<ans;
	return 0;
}

转移方程:\(f[i][k]=max(f[i-1][k],f[j][k-1]+a[i]-a[j]),j \in [1,i-1]\)

考虑到:\(f[j][k-1]-a[j],j \in [1,i-1]\)\(i\) 无关。

\(cmax[k]=max(f[j][k]-a[j]),j \in [0,i-1]\) .

\(f[i][k]=max(f[i-1][k],cmax[k]+a[i])\);

动态维护 \(cmax[]\) 即可 。

注意:

  • 倒序:可以把 \(cmax\) 降成一维。
  • 另外维护 cmax[0];

另一种解法:https://www.acwing.com/solution/content/5055/

股票买卖 V

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int f[N][2][2],a[N];
int n;
int main()
{
//	freopen("1.in","r",stdin);
    int i;
    scanf("%d",&n);
    f[0][1][1]=f[0][1][0]=f[0][0][1]=-1e9;
    for(i=1;i<=n;i++) 
        scanf("%d",&a[i]);
    for(i=1;i<=n;i++) {
        f[i][1][1]=f[i-1][0][0]-a[i];
        f[i][0][1]=max(f[i-1][1][1],f[i-1][1][0])+a[i];
        f[i][0][0]=max(f[i-1][0][0],f[i-1][0][1]);
        f[i][1][0]=max(f[i-1][1][0],f[i-1][1][1]);
    }
    cout<<max(max(f[n][1][1],f[n][0][0]),max(f[n][1][0],f[n][0][1]));
    return 0;
}

股票买卖 VI

#ifdef cjlworld
f[i] ---> 第i天能获得的最大利润
f[i] = max ---> f[i-1]  // 什么事都不干
	       ---> f[j]+a[i]-a[j]-f , 1<=j<=i-1; // j买 i卖 
#endif
#include<cstdio>
#include<iostream>
typedef long long LL;
typedef unsigned long long ULL;
using namespace std;
const int N=1e5+5;
int a[N],w;
int n;
int f[N],cmax; 
int main()
{
//	freopen("1.in","r",stdin);
	int i;
	scanf("%d%d",&n,&w);
	for(i=1;i<=n;i++) 
		scanf("%d",&a[i]);
	cmax=-a[1]-w;
	for(i=2;i<=n;i++) {
		f[i]=max(f[i-1],a[i]+cmax);
		cmax=max(cmax,f[i]-a[i]-w);
	}
	cout<<f[n];
	return 0;
}

低买

最长下降子序列 以及 最长下降子序列计数。

严格来讲不算是股票买卖的题目。

Solution:

  • \(f[i]\) 代表以 \(i\) 结尾的最长下降子序列长度。

  • \(g[i]\) 代表以 \(i\) 结尾的最长下降子序列个数。(具体而言,就是 \(f[i]\) 能由几个 \(f[j]\) 转移而来);

  • \(f[i]\) 就不讲了。对于一般的题目 \(g[i]= \sum_{j=0}^{i-1} [a[j]>a[i]\&\&f[j]+1==f[i]]*g[j]\);

  • 然而: 如果两种方案的买入日序列不同,但是价格序列相同,则认为这是相同的方案(只计算一次)。

  • 显然需要改变,对于相同的 \(a[j1]\)\(a[j2]\) (假设 \(f[i]\) 都可以通过 \(f[j1]\) \(f[j2]\) 转移得来);

  • \(j1\) , \(j2\) 是有交集的。

  • 但是对于同一个值中可转移的方案取 \(j\) 的最大值显然就包含的其他的方案。

  • 因为其他方案可以通过只将最后一位换成 \(j\) 变成 \(j\) 的方案。

  • 倒序。

Code:

#include<set>
#include<cstdio>
#include<iostream>
typedef long long LL;
typedef unsigned long long ULL;
using namespace std;
const int N=5000+5;
int a[N],f[N];
LL g[N];
int n;
set<int> S;
int main()
{
//	freopen("1.in","r",stdin);
	int i,j;
	scanf("%d",&n);
	for(i=1;i<=n;i++) 
		scanf("%d",&a[i]);
	a[0]=1e9+5;
	for(i=1;i<=n;i++) {
		for(j=0;j<=i-1;j++) 
			if(a[j]>a[i]) 
				f[i]=max(f[i],f[j]+1);
	}
//	for(i=1;i<=n;i++) 
//		cout<<f[i]<<" ";
//	cout<<endl;
//	for(i=2;i<=n;i++) 
//		f[i]=max(f[i],f[i-1]);
	g[0]=1;
	for(i=1;i<=n;i++) {
		S.clear();
		for(j=i-1;j>=0;j--) {
			if(a[i]<a[j]&&f[i]==f[j]+1&&(!S.count(a[j]))) {
				g[i]+=g[j];
				S.insert(a[j]);
			}
		}
	}
	int ans1=0; LL ans2=0;
	for(i=1;i<=n;i++) 
		ans1=max(ans1,f[i]);
	S.clear();
	for(i=n;i>=1;i--) 
		if(f[i]==ans1&&(!S.count(a[i])))
			ans2+=g[i],S.insert(a[i]);
//	cout<<f[n]<<" "<<g[n];
	cout<<ans1<<" "<<ans2;
	return 0;
}

时间复杂度:\(O(n^2logn)\)

另一种做法:https://www.acwing.com/solution/content/5093/

这种是一发现交集就把 \(j\) 较大的前半部分去掉。

1163. 纪念品

多支股票的股票买卖问题.
如果沿用上述状态机定义,问题不能得到良好解决。
但股票买卖问题有一个很好的性质,就是如果你把状态定义成全卖了,在后来的转移(第i天)时再判断第j天是否买入。
那么 (假设在第k天第一次买入了股票)收益为 \(a[i]-a[j]+a[j]-a[k]=a[i]-a[k]\) 等价于在第k天买,第i天卖。
因此第i天的状态只要考虑第 i-1 天的,就可以覆盖所有方案。

剩下就不难了。

#include<set>
#include<map>
#include<queue>
#include<stack>
#include<ctime>
#include<cmath>
#include<bitset>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;
typedef long long LL;
typedef unsigned long long ULL;

const int N=256,M=1e4+5;

int day,n,m;
int f[N]; // f[i] 表示在第i天,把所有纪念品都卖光的所获得的最大收入。
int a[N][N]; // a[i][j] 表示第 i 天第 j 种纪念品的价格。
int g[M];

int main()
{
//	freopen("1.in","r",stdin);
	int i,j,k;
	int x,y;
	
	cin>>day>>n>>m;
	for(i=1;i<=day;i++) 
		for(j=1;j<=n;j++) 
			cin>>a[i][j];

	f[1]=m;
	for(i=2;i<=day;i++) {
		f[i]=f[i-1]; // 这一天啥也不干。 
		
		memset(g,0,sizeof g); // 背包的辅助数组。
		// 背包问题 
		for(j=1;j<=n;j++) {
			if(a[i][j]>a[i-1][j]&&a[i-1][j]<=f[i-1]) 
				x=a[i][j]-a[i-1][j],y=a[i-1][j]; // 价值和体积。 
			else continue;
			
			for(k=y;k<=f[i-1];k++) 
				g[k]=max(g[k],g[k-y]+x);
		}
		f[i]=max(f[i],g[f[i-1]]+f[i-1]);
//		cout<<i<<" : "<<f[i]<<endl;
	}
	
	cout<<f[day]<<endl;
	return 0;
}
posted @ 2020-08-24 14:02  cjlworld  阅读(153)  评论(0编辑  收藏  举报